You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
//depot/UE4-TappyChicken/Engine/Source/... to //depot/UE4/Engine/Source/... [CL 2081906 by Peter Sauerbrei in Main branch]
644 lines
24 KiB
C#
644 lines
24 KiB
C#
// 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
|
|
string Target = AndroidPlatform.AndroidSdkApiTarget;
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
private void MakeAPK(string ProjectName, string ProjectDirectory, string OutputPath, string EngineDirectory, bool bForDistribution, string CookFlavor)
|
|
{
|
|
// cache some build product paths
|
|
string SourceSOName = OutputPath;
|
|
string DestApkName = Path.Combine(ProjectDirectory, "Binaries/Android/") + Path.GetFileNameWithoutExtension(SourceSOName) + ".apk";
|
|
|
|
// 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
|
|
string IntermediateAndroidPath = Path.Combine(ProjectDirectory, "Intermediate/Android/");
|
|
string UE4BuildPath = IntermediateAndroidPath + "APK";
|
|
string UE4BuildFilesPath = GetUE4BuildFilePath(EngineDirectory);
|
|
string GameBuildFilesPath = Path.Combine(ProjectDirectory, "Build/Android");
|
|
|
|
// 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);
|
|
|
|
// 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;
|
|
}
|
|
|
|
|
|
//Wipe the Intermediate/Build/APK directory first
|
|
DeleteDirectory(UE4BuildPath);
|
|
|
|
// 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)
|
|
{
|
|
string PAKFileLocation = ProjectDirectory + "/Saved/StagedBuilds/Android" + CookFlavor + "/" + ProjectName + "/Content/Paks";
|
|
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???
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// 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.
|
|
string GooglePlayServicesSourcePath = Path.GetFullPath(Path.Combine(EngineDirectory, "Build/Android/Java/google-play-services_lib/"));
|
|
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);
|
|
}
|
|
|
|
// 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);
|
|
Process NDKBuild = new Process();
|
|
NDKBuild.StartInfo = NDKBuildInfo;
|
|
NDKBuild.Start();
|
|
NDKBuild.WaitForExit();
|
|
|
|
// ndk build failure
|
|
if (NDKBuild.ExitCode != 0)
|
|
{
|
|
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);
|
|
Process CallAnt = new Process();
|
|
CallAnt.StartInfo = CallAntStartInfo;
|
|
CallAnt.Start();
|
|
CallAnt.WaitForExit();
|
|
|
|
// ant failure
|
|
if (CallAnt.ExitCode != 0)
|
|
{
|
|
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)
|
|
{
|
|
return PrepForUATPackageOrDeploy(InTarget.AppName, InTarget.ProjectDirectory, InTarget.OutputPath, BuildConfiguration.RelativeEnginePath, false, "");
|
|
}
|
|
|
|
public override bool PrepForUATPackageOrDeploy(string ProjectName, string ProjectDirectory, string ExecutablePath, string EngineDirectory, bool bForDistribution, string CookFlavor)
|
|
{
|
|
MakeAPK(ProjectName, ProjectDirectory, ExecutablePath, EngineDirectory, bForDistribution, CookFlavor);
|
|
return true;
|
|
}
|
|
|
|
public static void OutputReceivedDataEventHandler(Object Sender, DataReceivedEventArgs Line)
|
|
{
|
|
if ((Line != null) && (Line.Data != null))
|
|
{
|
|
Log.TraceInformation(Line.Data);
|
|
}
|
|
}
|
|
}
|
|
}
|