2014-03-14 14:13:41 -04:00
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.Xml ;
using System.Diagnostics ;
using System.IO ;
using Microsoft.Win32 ;
namespace UnrealBuildTool.Android
{
class UEDeployAndroid : UEBuildDeploy
{
/ * *
* Register the platform with the UEBuildDeploy class
* /
public override void RegisterBuildDeploy ( )
{
string NDKPath = Environment . GetEnvironmentVariable ( "ANDROID_HOME" ) ;
// we don't have an NDKROOT specified
if ( String . IsNullOrEmpty ( NDKPath ) )
{
Log . TraceVerbose ( " Unable to register Android deployment class because the ANDROID_HOME environment variable isn't set or points to something that doesn't exist" ) ;
}
else
{
UEBuildDeploy . RegisterBuildDeploy ( UnrealTargetPlatform . Android , this ) ;
}
}
/** Internal usage for GetApiLevel */
private static List < string > PossibleApiLevels = null ;
/** Simple function to pipe output asynchronously */
private static void ParseApiLevel ( 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 ) )
{
string Line = Event . Data ;
if ( Line . StartsWith ( "id:" ) )
{
// the line should look like: id: 1 or "android-19"
string [ ] Tokens = Line . Split ( "\"" . ToCharArray ( ) ) ;
if ( Tokens . Length > = 2 )
{
PossibleApiLevels . Add ( Tokens [ 1 ] ) ;
}
}
}
}
static private bool bHasPrintedApiLevel = false ;
private static string GetSdkApiLevel ( )
{
// default to looking on disk for latest API level
2014-04-23 18:00:27 -04:00
string Target = AndroidPlatform . AndroidSdkApiTarget ;
2014-03-14 14:13:41 -04:00
// if we want to use whatever version the ndk uses, then use that
if ( Target = = "matchndk" )
{
Target = AndroidToolChain . GetNdkApiLevel ( ) ;
}
// run a command and capture output
if ( Target = = "latest" )
{
// we expect there to be one, so use the first one
string AndroidCommandPath = Environment . ExpandEnvironmentVariables ( "%ANDROID_HOME%/tools/android.bat" ) ;
var ExeInfo = new ProcessStartInfo ( AndroidCommandPath , "list targets" ) ;
ExeInfo . UseShellExecute = false ;
ExeInfo . RedirectStandardOutput = true ;
using ( var GameProcess = Process . Start ( ExeInfo ) )
{
PossibleApiLevels = new List < string > ( ) ;
GameProcess . BeginOutputReadLine ( ) ;
GameProcess . OutputDataReceived + = ParseApiLevel ;
GameProcess . WaitForExit ( ) ;
}
if ( PossibleApiLevels ! = null & & PossibleApiLevels . Count > 0 )
{
Target = AndroidToolChain . GetLargestApiLevel ( PossibleApiLevels . ToArray ( ) ) ;
}
else
{
throw new BuildException ( "Can't make an APK an API installed (see \"android.bat list targets\")" ) ;
}
}
if ( ! bHasPrintedApiLevel )
{
Console . WriteLine ( "Building with SDK API '{0}'" , Target ) ;
bHasPrintedApiLevel = true ;
}
return Target ;
}
private static string GetAntPath ( )
{
// look up an ANT_HOME env var
string AntHome = Environment . GetEnvironmentVariable ( "ANT_HOME" ) ;
if ( ! string . IsNullOrEmpty ( AntHome ) & & Directory . Exists ( AntHome ) )
{
string AntPath = AntHome + "/bin/ant.bat" ;
// use it if found
if ( File . Exists ( AntPath ) )
{
return AntPath ;
}
}
// otherwise, look in the eclipse install for the ant plugin (matches the unzipped Android ADT from Google)
string PluginDir = Environment . ExpandEnvironmentVariables ( "%ANDROID_HOME%/../eclipse/plugins" ) ;
if ( Directory . Exists ( PluginDir ) )
{
string [ ] Plugins = Directory . GetDirectories ( PluginDir , "org.apache.ant*" ) ;
// use the first one with ant.bat
if ( Plugins . Length > 0 )
{
foreach ( string PluginName in Plugins )
{
string AntPath = PluginName + "/bin/ant.bat" ;
// use it if found
if ( File . Exists ( AntPath ) )
{
return AntPath ;
}
}
}
}
throw new BuildException ( "Unable to find ant.bat (via %ANT_HOME% or %ANDROID_HOME%/../eclipse/plugins/org.apache.ant*" ) ;
}
private static void CopyFileDirectory ( string SourceDir , string DestDir , Dictionary < string , string > Replacements )
{
if ( ! Directory . Exists ( SourceDir ) )
{
return ;
}
string [ ] Files = Directory . GetFiles ( SourceDir , "*.*" , SearchOption . AllDirectories ) ;
foreach ( string Filename in Files )
{
// skip template files
if ( Path . GetExtension ( Filename ) = = ".template" )
{
continue ;
}
// make the dst filename with the same structure as it was in SourceDir
string DestFilename = Path . Combine ( DestDir , Utils . MakePathRelativeTo ( Filename , SourceDir ) ) ;
if ( File . Exists ( DestFilename ) )
{
File . Delete ( DestFilename ) ;
}
// make the subdirectory if needed
string DestSubdir = Path . GetDirectoryName ( DestFilename ) ;
if ( ! Directory . Exists ( DestSubdir ) )
{
Directory . CreateDirectory ( DestSubdir ) ;
}
// some files are handled specially
string Ext = Path . GetExtension ( Filename ) ;
if ( Ext = = ".xml" )
{
string Contents = File . ReadAllText ( Filename ) ;
// replace some varaibles
foreach ( var Pair in Replacements )
{
Contents = Contents . Replace ( Pair . Key , Pair . Value ) ;
}
// write out file
File . WriteAllText ( DestFilename , Contents ) ;
}
else
{
File . Copy ( Filename , DestFilename ) ;
// remove any read only flags
FileInfo DestFileInfo = new FileInfo ( DestFilename ) ;
DestFileInfo . Attributes = DestFileInfo . Attributes & ~ FileAttributes . ReadOnly ;
}
}
}
2014-04-23 19:40:10 -04:00
private void DeleteDirectory ( string Path )
{
Console . WriteLine ( "\nDeleting: " + Path ) ;
if ( Directory . Exists ( Path ) )
{
try
{
// first make sure everything it writable
string [ ] Files = Directory . GetFiles ( Path , "*.*" , SearchOption . AllDirectories ) ;
foreach ( string Filename in Files )
{
// remove any read only flags
FileInfo FileInfo = new FileInfo ( Filename ) ;
FileInfo . Attributes = FileInfo . Attributes & ~ FileAttributes . ReadOnly ;
}
// now deleting will work better
Directory . Delete ( Path , true ) ;
}
catch ( Exception )
{
Log . TraceInformation ( "Failed to delete intermediate cdirectory {0}. Continuing on..." , Path ) ;
}
}
}
2014-05-13 11:40:41 -04:00
public string GetUE4BuildFilePath ( String EngineDirectory )
{
return Path . GetFullPath ( Path . Combine ( EngineDirectory , "Build/Android/Java" ) ) ;
}
public string GetUE4JavaFilePath ( String EngineDirectory )
{
return Path . GetFullPath ( Path . Combine ( GetUE4BuildFilePath ( EngineDirectory ) , "src/com/epicgames/ue4" ) ) ;
}
public string GetUE4JavaBuildSettingsFileName ( String EngineDirectory )
{
return Path . Combine ( GetUE4JavaFilePath ( EngineDirectory ) , "JavaBuildSettings.java" ) ;
}
public void WriteJavaBuildSettingsFile ( string FileName , bool OBBinAPK )
{
// (!UEBuildConfiguration.bOBBinAPK ? "PackageType.AMAZON" : /*bPackageForGoogle ? "PackageType.GOOGLE" :*/ "PackageType.DEVELOPMENT") + ";\n");
string Setting = OBBinAPK ? "AMAZON" : "DEVELOPMENT" ;
if ( ! File . Exists ( FileName ) | | ShouldWriteJavaBuildSettingsFile ( FileName , Setting ) )
{
StringBuilder BuildSettings = new StringBuilder ( "package com.epicgames.ue4;\npublic class JavaBuildSettings\n{\n" ) ;
BuildSettings . Append ( "\tpublic enum PackageType {AMAZON, GOOGLE, DEVELOPMENT};\n" ) ;
BuildSettings . Append ( "\tpublic static final PackageType PACKAGING = PackageType." + Setting + ";\n" ) ;
BuildSettings . Append ( "}\n" ) ;
File . WriteAllText ( FileName , BuildSettings . ToString ( ) ) ;
}
else
{
Console . WriteLine ( "::Didn't write config file; contains same data as before." ) ;
}
}
public bool ShouldWriteJavaBuildSettingsFile ( string FileName , string setting )
{
var fileContent = File . ReadAllLines ( FileName ) ;
var packageLine = fileContent [ 4 ] ; // We know this to be true... because we write it below...
int location = packageLine . IndexOf ( "PACKAGING" ) + 12 + 12 ; // + (PACKAGING = ) + ("PackageType.")
return String . Compare ( setting , packageLine . Substring ( location ) ) ! = 0 ;
}
2014-05-22 09:13:12 -04:00
private void MakeAPK ( string ProjectName , string ProjectDirectory , string OutputPath , string EngineDirectory , bool bForDistribution , string CookFlavor )
2014-03-14 14:13:41 -04:00
{
// cache some build product paths
string SourceSOName = OutputPath ;
2014-04-23 19:40:10 -04:00
string DestApkName = Path . Combine ( ProjectDirectory , "Binaries/Android/" ) + Path . GetFileNameWithoutExtension ( SourceSOName ) + ".apk" ;
2014-03-14 14:13:41 -04:00
// if the source binary was UE4Game, replace it with the new project name, when re-packaging a binary only build
DestApkName = DestApkName . Replace ( "UE4Game-" , ProjectName + "-" ) ;
// cache some tools paths
string AndroidCommandPath = Environment . ExpandEnvironmentVariables ( "%ANDROID_HOME%/tools/android.bat" ) ;
string NDKBuildPath = Environment . ExpandEnvironmentVariables ( "%NDKROOT%/ndk-build.cmd" ) ;
string AntBuildPath = GetAntPath ( ) ;
// set up some directory info
2014-04-23 19:40:10 -04:00
string IntermediateAndroidPath = Path . Combine ( ProjectDirectory , "Intermediate/Android/" ) ;
string UE4BuildPath = IntermediateAndroidPath + "APK" ;
2014-05-13 11:40:41 -04:00
string UE4BuildFilesPath = GetUE4BuildFilePath ( EngineDirectory ) ;
2014-04-23 19:40:10 -04:00
string GameBuildFilesPath = Path . Combine ( ProjectDirectory , "Build/Android" ) ;
2014-04-02 18:09:23 -04:00
2014-05-13 11:40:41 -04:00
// See if we need to create a 'default' Java Build settings file if one doesn't exist (if it does exist we have to assume it has been setup correctly)
string UE4JavaBuildSettingsFileName = GetUE4JavaBuildSettingsFileName ( EngineDirectory ) ;
WriteJavaBuildSettingsFile ( UE4JavaBuildSettingsFileName , UEBuildConfiguration . bOBBinAPK ) ;
2014-04-02 18:09:23 -04:00
// check to see if it's out of date before trying the slow make apk process (look at .so and all Engine and Project build files to be safe)
List < String > InputFiles = new List < string > ( ) ;
InputFiles . Add ( SourceSOName ) ;
InputFiles . AddRange ( Directory . EnumerateFiles ( UE4BuildFilesPath , "*.*" , SearchOption . AllDirectories ) ) ;
if ( Directory . Exists ( GameBuildFilesPath ) )
{
InputFiles . AddRange ( Directory . EnumerateFiles ( GameBuildFilesPath , "*.*" , SearchOption . AllDirectories ) ) ;
}
// look for any newer input file
DateTime ApkTime = File . GetLastWriteTimeUtc ( DestApkName ) ;
bool bAllInputsCurrent = true ;
foreach ( var InputFileName in InputFiles )
{
DateTime InputFileTime = File . GetLastWriteTimeUtc ( InputFileName ) ;
if ( InputFileTime . CompareTo ( ApkTime ) > 0 )
{
// could break here
bAllInputsCurrent = false ;
break ;
}
}
if ( bAllInputsCurrent )
{
Log . TraceInformation ( "{0} is up to date (compared to the .so and .java input files)" , DestApkName ) ;
return ;
}
2014-03-14 14:13:41 -04:00
//Wipe the Intermediate/Build/APK directory first
2014-04-23 19:40:10 -04:00
DeleteDirectory ( UE4BuildPath ) ;
2014-03-14 14:13:41 -04:00
2014-05-13 11:40:41 -04:00
// If we are packaging for Amazon then we need to copy the PAK files to the correct location
// Currently we'll just support 1 of 'em
if ( UEBuildConfiguration . bOBBinAPK )
{
2014-05-22 09:13:12 -04:00
string PAKFileLocation = ProjectDirectory + "/Saved/StagedBuilds/Android" + CookFlavor + "/" + ProjectName + "/Content/Paks" ;
2014-05-13 11:40:41 -04:00
Console . WriteLine ( "Pak location {0}" , PAKFileLocation ) ;
string PAKFileDestination = UE4BuildPath + "/assets" ;
Console . WriteLine ( "Pak destination location {0}" , PAKFileDestination ) ;
if ( Directory . Exists ( PAKFileLocation ) )
{
Directory . CreateDirectory ( UE4BuildPath ) ;
Directory . CreateDirectory ( PAKFileDestination ) ;
Console . WriteLine ( "PAK file exists..." ) ;
var pakFiles = Directory . EnumerateFiles ( PAKFileLocation , "*.pak" , SearchOption . TopDirectoryOnly ) ;
foreach ( var s in pakFiles )
{
Console . WriteLine ( "Found file {0}" , s ) ;
}
if ( pakFiles . Count ( ) > 0 )
{
var destFileName = Path . Combine ( PAKFileDestination , Path . GetFileName ( pakFiles . ElementAt ( 0 ) ) + ".png" ) ; // Need a rename to turn off compression
var srcFileName = pakFiles . ElementAt ( 0 ) ;
if ( ! File . Exists ( destFileName ) | | File . GetLastWriteTimeUtc ( destFileName ) < File . GetLastWriteTimeUtc ( srcFileName ) )
{
Console . WriteLine ( "Copying {0} to {1}" , srcFileName , destFileName ) ;
File . Copy ( srcFileName , destFileName ) ;
}
}
}
// Do we want to kill the OBB here or not???
}
2014-03-14 14:13:41 -04:00
Dictionary < string , string > Replacements = new Dictionary < string , string > ( ) ;
Replacements . Add ( "${EXECUTABLE_NAME}" , ProjectName ) ;
// distribution apps can't be debuggable, so if it was set to true, set it to false:
if ( bForDistribution )
{
Replacements . Add ( "android:debuggable=\"true\"" , "android:debuggable=\"false\"" ) ;
}
//Copy build files to the intermediate folder in this order (later overrides earlier):
// - Shared Engine
// - Shared Engine NoRedist (for Epic secret files)
// - Game
// - Game NoRedist (for Epic secret files)
CopyFileDirectory ( UE4BuildFilesPath , UE4BuildPath , Replacements ) ;
CopyFileDirectory ( UE4BuildFilesPath + "/NoRedist" , UE4BuildPath , Replacements ) ;
CopyFileDirectory ( GameBuildFilesPath , UE4BuildPath , Replacements ) ;
CopyFileDirectory ( GameBuildFilesPath + "/NoRedist" , UE4BuildPath , Replacements ) ;
// Copy the generated .so file from the binaries directory to the jni folder
if ( ! File . Exists ( SourceSOName ) )
{
throw new BuildException ( "Can't make an APK without the compiled .so [{0}]" , SourceSOName ) ;
}
if ( ! Directory . Exists ( UE4BuildPath + "/jni" ) )
{
throw new BuildException ( "Can't make an APK without the jni directory [{0}/jni]" , UE4BuildFilesPath ) ;
}
//Android.bat for game-specific
ProcessStartInfo AndroidBatStartInfoGame = new ProcessStartInfo ( ) ;
AndroidBatStartInfoGame . WorkingDirectory = UE4BuildPath ;
AndroidBatStartInfoGame . FileName = AndroidCommandPath ;
AndroidBatStartInfoGame . Arguments = "update project --name " + ProjectName + " --path . --target " + GetSdkApiLevel ( ) ;
AndroidBatStartInfoGame . UseShellExecute = false ;
Console . WriteLine ( "\nRunning: " + AndroidBatStartInfoGame . FileName + " " + AndroidBatStartInfoGame . Arguments ) ;
Process AndroidBatGame = new Process ( ) ;
AndroidBatGame . StartInfo = AndroidBatStartInfoGame ;
AndroidBatGame . Start ( ) ;
AndroidBatGame . WaitForExit ( ) ;
// android bat failure
if ( AndroidBatGame . ExitCode ! = 0 )
{
throw new BuildException ( "android.bat failed [{0}]" , AndroidBatStartInfoGame . Arguments ) ;
}
2014-04-23 19:40:10 -04:00
// Update the Google Play services lib with the target platform version currently in use.
// This appears to be required for the build to work without errors when Play services are referenced by the game.
// This will try to modify existing files, like project.properties, so we copy the entire library into
// an intermediate directory and work from there.
2014-04-23 20:11:35 -04:00
string GooglePlayServicesSourcePath = Path . GetFullPath ( Path . Combine ( EngineDirectory , "Build/Android/Java/google-play-services_lib/" ) ) ;
2014-04-23 19:40:10 -04:00
string GooglePlayServicesIntermediatePath = Path . GetFullPath ( Path . Combine ( IntermediateAndroidPath , "google-play-services_lib/" ) ) ;
DeleteDirectory ( GooglePlayServicesIntermediatePath ) ;
CopyFileDirectory ( GooglePlayServicesSourcePath , GooglePlayServicesIntermediatePath , new Dictionary < string , string > ( ) ) ;
ProcessStartInfo AndroidBatStartInfoPlayServicesLib = new ProcessStartInfo ( ) ;
AndroidBatStartInfoPlayServicesLib . WorkingDirectory = GooglePlayServicesIntermediatePath ;
AndroidBatStartInfoPlayServicesLib . FileName = AndroidCommandPath ;
AndroidBatStartInfoPlayServicesLib . Arguments = "update project " + " --path . --target " + GetSdkApiLevel ( ) ;
AndroidBatStartInfoPlayServicesLib . UseShellExecute = false ;
Console . WriteLine ( "\nRunning: " + AndroidBatStartInfoPlayServicesLib . FileName + " " + AndroidBatStartInfoPlayServicesLib . Arguments ) ;
Process AndroidBatPlayServicesLib = new Process ( ) ;
AndroidBatPlayServicesLib . StartInfo = AndroidBatStartInfoPlayServicesLib ;
AndroidBatPlayServicesLib . Start ( ) ;
AndroidBatPlayServicesLib . WaitForExit ( ) ;
// android bat failure
if ( AndroidBatPlayServicesLib . ExitCode ! = 0 )
{
throw new BuildException ( "android.bat failed [{0}]" , AndroidBatStartInfoPlayServicesLib . Arguments ) ;
}
//need to create separate run for each lib. Will be added to project.properties in order in which they are added
//the order is important.
//as android.library.reference.X=libpath where X = 1 - N
//for e.g this one will be added as android.library.reference.1=<EngineDirectory>/Source/ThirdParty/Android/google_play_services_lib
// Ant seems to need a relative path to work
Uri ServicesBuildUri = new Uri ( GooglePlayServicesIntermediatePath ) ;
Uri ProjectUri = new Uri ( UE4BuildPath + "/" ) ;
string RelativeServicesUri = ProjectUri . MakeRelativeUri ( ServicesBuildUri ) . ToString ( ) ;
AndroidBatStartInfoGame . Arguments = " update project --name " + ProjectName + " --path . --target " + GetSdkApiLevel ( ) + " --library " + RelativeServicesUri ;
Console . WriteLine ( "\nRunning: " + AndroidBatStartInfoGame . FileName + " " + AndroidBatStartInfoGame . Arguments ) ;
AndroidBatGame . Start ( ) ;
AndroidBatGame . WaitForExit ( ) ;
if ( AndroidBatGame . ExitCode ! = 0 )
{
throw new BuildException ( "android.bat failed [{0}]" , AndroidBatStartInfoGame . Arguments ) ;
}
2014-03-14 14:13:41 -04:00
// Use ndk-build to do stuff and move the .so file to the lib folder (only if NDK is installed)
string FinalSOName = "" ;
if ( File . Exists ( NDKBuildPath ) )
{
// copy the binary to the standard .so location
FinalSOName = UE4BuildPath + "/jni/libUE4.so" ;
File . Copy ( SourceSOName , FinalSOName , true ) ;
ProcessStartInfo NDKBuildInfo = new ProcessStartInfo ( ) ;
NDKBuildInfo . WorkingDirectory = UE4BuildPath ;
NDKBuildInfo . FileName = NDKBuildPath ;
if ( ! bForDistribution )
{
NDKBuildInfo . Arguments = "NDK_DEBUG=1" ;
}
NDKBuildInfo . UseShellExecute = true ;
NDKBuildInfo . WindowStyle = ProcessWindowStyle . Minimized ;
Console . WriteLine ( "\nRunning: " + NDKBuildInfo . FileName + " " + NDKBuildInfo . Arguments ) ;
2014-08-04 08:53:45 -04:00
Process NDKBuild = new Process ( ) ;
NDKBuild . StartInfo = NDKBuildInfo ;
NDKBuild . Start ( ) ;
NDKBuild . WaitForExit ( ) ;
2014-03-14 14:13:41 -04:00
// ndk build failure
2014-08-04 08:53:45 -04:00
if ( NDKBuild . ExitCode ! = 0 )
2014-03-14 14:13:41 -04:00
{
throw new BuildException ( "ndk-build failed [{0}]" , NDKBuildInfo . Arguments ) ;
}
}
else
{
// if no NDK, we don't need any of the debugger stuff, so we just copy the .so to where it will end up
FinalSOName = UE4BuildPath + "/libs/armeabi-v7a/libUE4.so" ;
Directory . CreateDirectory ( Path . GetDirectoryName ( FinalSOName ) ) ;
File . Copy ( SourceSOName , FinalSOName ) ;
}
// remove any read only flags
FileInfo DestFileInfo = new FileInfo ( FinalSOName ) ;
DestFileInfo . Attributes = DestFileInfo . Attributes & ~ FileAttributes . ReadOnly ;
// Use ant debug to build the .apk file
ProcessStartInfo CallAntStartInfo = new ProcessStartInfo ( ) ;
CallAntStartInfo . WorkingDirectory = UE4BuildPath ;
CallAntStartInfo . FileName = "cmd.exe" ;
CallAntStartInfo . Arguments = "/c \"" + AntBuildPath + "\" " + ( bForDistribution ? "release" : "debug" ) ;
CallAntStartInfo . UseShellExecute = false ;
Console . WriteLine ( "\nRunning: " + CallAntStartInfo . Arguments ) ;
2014-08-04 08:53:45 -04:00
Process CallAnt = new Process ( ) ;
CallAnt . StartInfo = CallAntStartInfo ;
CallAnt . Start ( ) ;
CallAnt . WaitForExit ( ) ;
2014-03-14 14:13:41 -04:00
// ant failure
2014-08-04 08:53:45 -04:00
if ( CallAnt . ExitCode ! = 0 )
2014-03-14 14:13:41 -04:00
{
throw new BuildException ( "ant.bat failed [{0}]" , CallAntStartInfo . Arguments ) ;
}
// make sure destination exists
Directory . CreateDirectory ( Path . GetDirectoryName ( DestApkName ) ) ;
// do we need to sign for distro?
if ( bForDistribution )
{
// use diffeent source and dest apk's for signed mode
string SourceApkName = UE4BuildPath + "/bin/" + ProjectName + "-release-unsigned.apk" ;
SignApk ( UE4BuildPath + "/SigningConfig.xml" , SourceApkName , DestApkName ) ;
}
else
{
// now copy to the final location
File . Copy ( UE4BuildPath + "/bin/" + ProjectName + "-debug" + ".apk" , DestApkName , true ) ;
}
}
private void SignApk ( string ConfigFilePath , string SourceApk , string DestApk )
{
string JarCommandPath = Environment . ExpandEnvironmentVariables ( "%JAVA_HOME%/bin/jarsigner.exe" ) ;
string ZipalignCommandPath = Environment . ExpandEnvironmentVariables ( "%ANDROID_HOME%/tools/zipalign.exe" ) ;
if ( ! File . Exists ( ConfigFilePath ) )
{
throw new BuildException ( "Unable to sign for Shipping without signing config file: '{0}" , ConfigFilePath ) ;
}
// open an Xml parser for the config file
string ConfigFile = File . ReadAllText ( ConfigFilePath ) ;
XmlReader Xml = XmlReader . Create ( new StringReader ( ConfigFile ) ) ;
string Alias = "UESigningKey" ;
string KeystorePassword = "" ;
string KeyPassword = "_sameaskeystore_" ;
string Keystore = "UE.keystore" ;
Xml . ReadToFollowing ( "ue-signing-config" ) ;
bool bFinishedSection = false ;
while ( Xml . Read ( ) & & ! bFinishedSection )
{
switch ( Xml . NodeType )
{
case XmlNodeType . Element :
if ( Xml . Name = = "keyalias" )
{
Alias = Xml . ReadElementContentAsString ( ) ;
}
else if ( Xml . Name = = "keystorepassword" )
{
KeystorePassword = Xml . ReadElementContentAsString ( ) ;
}
else if ( Xml . Name = = "keypassword" )
{
KeyPassword = Xml . ReadElementContentAsString ( ) ;
}
else if ( Xml . Name = = "keystore" )
{
Keystore = Xml . ReadElementContentAsString ( ) ;
}
break ;
case XmlNodeType . EndElement :
if ( Xml . Name = = "ue-signing-config" )
{
bFinishedSection = true ;
}
break ;
}
}
string CommandLine = "-sigalg SHA1withRSA -digestalg SHA1" ;
CommandLine + = " -storepass " + KeystorePassword ;
if ( KeyPassword ! = "_sameaskeystore_" )
{
CommandLine + = " -keypass " + KeyPassword ;
}
// put on the keystore
CommandLine + = " -keystore \"" + Keystore + "\"" ;
// finish off the commandline
CommandLine + = " \"" + SourceApk + "\" " + Alias ;
// sign in-place
ProcessStartInfo CallJarStartInfo = new ProcessStartInfo ( ) ;
CallJarStartInfo . WorkingDirectory = Path . GetDirectoryName ( ConfigFilePath ) ;
CallJarStartInfo . FileName = JarCommandPath ;
CallJarStartInfo . Arguments = CommandLine ; // string.Format("galg SHA1withRSA -digestalg SHA1 -keystore {1} {2} {3}", Password, Keystore, SourceApk, Alias);
CallJarStartInfo . UseShellExecute = false ;
Console . WriteLine ( "\nRunning: {0} {1}" , CallJarStartInfo . FileName , CallJarStartInfo . Arguments ) ;
Process CallAnt = new Process ( ) ;
CallAnt . StartInfo = CallJarStartInfo ;
CallAnt . Start ( ) ;
CallAnt . WaitForExit ( ) ;
if ( File . Exists ( DestApk ) )
{
File . Delete ( DestApk ) ;
}
// now we need to zipalign the apk to the final destination (to 4 bytes, must be 4)
ProcessStartInfo CallZipStartInfo = new ProcessStartInfo ( ) ;
CallZipStartInfo . WorkingDirectory = Path . GetDirectoryName ( ConfigFilePath ) ;
CallZipStartInfo . FileName = ZipalignCommandPath ;
CallZipStartInfo . Arguments = string . Format ( "4 \"{0}\" \"{1}\"" , SourceApk , DestApk ) ;
CallZipStartInfo . UseShellExecute = false ;
Console . WriteLine ( "\nRunning: {0} {1}" , CallZipStartInfo . FileName , CallZipStartInfo . Arguments ) ;
Process CallZip = new Process ( ) ;
CallZip . StartInfo = CallZipStartInfo ;
CallZip . Start ( ) ;
CallZip . WaitForExit ( ) ;
}
public override bool PrepTargetForDeployment ( UEBuildTarget InTarget )
{
2014-05-22 09:13:12 -04:00
return PrepForUATPackageOrDeploy ( InTarget . AppName , InTarget . ProjectDirectory , InTarget . OutputPath , BuildConfiguration . RelativeEnginePath , false , "" ) ;
2014-03-14 14:13:41 -04:00
}
2014-05-22 09:13:12 -04:00
public override bool PrepForUATPackageOrDeploy ( string ProjectName , string ProjectDirectory , string ExecutablePath , string EngineDirectory , bool bForDistribution , string CookFlavor )
2014-03-14 14:13:41 -04:00
{
2014-05-22 09:13:12 -04:00
MakeAPK ( ProjectName , ProjectDirectory , ExecutablePath , EngineDirectory , bForDistribution , CookFlavor ) ;
2014-03-14 14:13:41 -04:00
return true ;
}
public static void OutputReceivedDataEventHandler ( Object Sender , DataReceivedEventArgs Line )
{
if ( ( Line ! = null ) & & ( Line . Data ! = null ) )
{
Log . TraceInformation ( Line . Data ) ;
}
}
}
}