Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/Android/AndroidPlatform.Automation.cs
Peter Sauerbrei aa5a1232f1 Merging
//depot/UE4-TappyChicken/Engine/Source/...

to //depot/UE4/Engine/Source/...

[CL 2081906 by Peter Sauerbrei in Main branch]
2014-05-22 09:13:12 -04:00

458 lines
16 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.IO;
using System.Diagnostics;
using System.Net.NetworkInformation;
using AutomationTool;
using UnrealBuildTool;
public class AndroidPlatform : Platform
{
public AndroidPlatform()
:base(UnrealTargetPlatform.Android)
{
}
private static string GetArchitecture(ProjectParams Params)
{
// @todo android: Need the architecture passed through from the ProjectParams!
return "-armv7";
}
private static string GetFinalSOName(ProjectParams Params, string DecoratedExeName)
{
return Path.Combine(Path.GetDirectoryName(Params.ProjectGameExeFilename), DecoratedExeName) + GetArchitecture(Params) + ".so";
}
private static string GetFinalApkName(ProjectParams Params, string DecoratedExeName, bool bRenameUE4Game)
{
string ProjectDir = Path.GetDirectoryName(Path.GetFullPath(Params.RawProjectPath));
// Apk's go to project location, not necessarily where the .so is (content only packages need to output to their directory)
string ApkName = Path.Combine(ProjectDir, "Binaries/Android", DecoratedExeName) + GetArchitecture(Params) + ".apk";
// if the source binary was UE4Game, handle using it or switching to project name
if (Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename) == "UE4Game")
{
if (bRenameUE4Game)
{
// replace UE4Game with project name (only replace in the filename part)
ApkName = Path.Combine(Path.GetDirectoryName(ApkName), Path.GetFileName(ApkName).Replace("UE4Game", Params.ShortProjectName));
}
else
{
// if we want to use UE4 directly then use it from the engine directory not project directory
ApkName = ApkName.Replace(ProjectDir, Path.Combine(CmdEnv.LocalRoot, "Engine"));
}
}
Console.WriteLine("APKName = " + ApkName + " ::: SO name = " + GetFinalSOName(Params, DecoratedExeName));
return ApkName;
}
private static string GetFinalObbName(string ApkName)
{
// calculate the name for the .obb file
string PackageName = GetPackageInfo(ApkName, false);
if (PackageName == null)
{
throw new AutomationException("Failed to get package name from " + ApkName);
}
string PackageVersion = GetPackageInfo(ApkName, true);
if (PackageVersion == null || PackageVersion.Length == 0)
{
throw new AutomationException("Failed to get package version from " + ApkName);
}
if (PackageVersion.Length > 0)
{
int IntVersion = int.Parse(PackageVersion);
PackageVersion = IntVersion.ToString("00000");
}
string ObbName = string.Format("main.{0}.{1}.obb", PackageVersion, PackageName);
// plop the .obb right next to the executable
ObbName = Path.Combine(Path.GetDirectoryName(ApkName), ObbName);
return ObbName;
}
private static string GetDeviceObbName(string ApkName)
{
string ObbName = GetFinalObbName(ApkName);
string PackageName = GetPackageInfo(ApkName, false);
return "/mnt/sdcard/obb/" + PackageName + "/" + Path.GetFileName(ObbName);
}
private static string GetFinalBatchName(string ApkName, ProjectParams Params)
{
return Path.Combine(Path.GetDirectoryName(ApkName), "Install_" + Params.ShortProjectName + "_" + Params.ClientConfigsToBuild[0].ToString() + ".bat");
}
public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL)
{
string SOName = GetFinalSOName(Params, SC.StageExecutables[0]);
string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true);
string BatchName = GetFinalBatchName(ApkName, Params);
// packaging just takes a pak file and makes it the .obb
UEBuildConfiguration.bOBBinAPK = Params.OBBinAPK; // Make sure this setting is sync'd pre-build
var Deploy = UEBuildDeploy.GetBuildDeploy(UnrealTargetPlatform.Android);
string CookFlavor = SC.CookPlatform.IndexOf("_") > 0 ? SC.CookPlatform.Substring(SC.CookPlatform.IndexOf("_")) : "";
Deploy.PrepForUATPackageOrDeploy(Params.ShortProjectName, SC.ProjectRoot, SOName, SC.LocalRoot + "/Engine", Params.Distribution, CookFlavor);
// first, look for a .pak file in the staged directory
string[] PakFiles = Directory.GetFiles(SC.StageDirectory, "*.pak", SearchOption.AllDirectories);
// for now, we only support 1 pak/obb file
if (PakFiles.Length != 1)
{
throw new AutomationException("Can't package for Android with 0 or more than 1 pak file (found {0} pak files in {1})", PakFiles.Length, SC.StageDirectory);
}
string LocalObbName = GetFinalObbName(ApkName);
string DeviceObbName = GetDeviceObbName(ApkName);
// Always delete the target OBB file if it exists
if (File.Exists(LocalObbName))
{
File.Delete(LocalObbName);
}
if (!Params.OBBinAPK)
{
Log("Creating {0} from {1}", LocalObbName, PakFiles[0]);
File.Copy(PakFiles[0], LocalObbName);
}
Log("Writing bat for install with {0}", Params.OBBinAPK ? "OBB in APK" : "OBB separate");
string PackageName = GetPackageInfo(ApkName, false);
// make a batch file that can be used to install the .apk and .obb files
string[] BatchLines = new string[] {
"setlocal",
"set ADB=%ANDROID_HOME%\\platform-tools\\adb.exe",
"%ADB% uninstall " + PackageName,
"%ADB% install " + Path.GetFileName(ApkName),
"%ADB% shell rm -rf /mnt/sdcard/" + Params.ShortProjectName,
"%ADB% shell rm -rf /mnt/sdcard/UE4Game/UE4CommandLine.txt", // we need to delete the commandline in UE4Game or it will mess up loading
"%ADB% shell rm -rf /mnt/sdcard/obb/" + PackageName,
Params.OBBinAPK ? "" : "%ADB% push " + Path.GetFileName(LocalObbName) + " " + DeviceObbName,
};
File.WriteAllLines(BatchName, BatchLines);
PrintRunTime();
}
public override void GetFilesToArchive(ProjectParams Params, DeploymentContext SC)
{
if (SC.StageTargetConfigurations.Count != 1)
{
throw new AutomationException("Android is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations", SC.StageTargetConfigurations.Count);
}
var TargetConfiguration = SC.StageTargetConfigurations[0];
string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true);
string ObbName = GetFinalObbName(ApkName);
string BatchName = GetFinalBatchName(ApkName, Params);
// verify the files exist
if (!FileExists(ApkName))
{
throw new AutomationException("ARCHIVE FAILED - {0} was not found", ApkName);
}
if (!Params.OBBinAPK && !FileExists(ObbName))
{
throw new AutomationException("ARCHIVE FAILED - {0} was not found", ObbName);
}
SC.ArchiveFiles(Path.GetDirectoryName(ApkName), Path.GetFileName(ApkName));
if(!Params.OBBinAPK)
{
SC.ArchiveFiles(Path.GetDirectoryName(ObbName), Path.GetFileName(ObbName));
}
SC.ArchiveFiles(Path.GetDirectoryName(BatchName), Path.GetFileName(BatchName));
}
private string GetAdbCommand(ProjectParams Params)
{
string SerialNumber = Params.Device;
if (SerialNumber.Contains("@"))
{
// get the bit after the @
SerialNumber = SerialNumber.Split("@".ToCharArray())[1];
}
if (SerialNumber != "")
{
SerialNumber = " -s " + SerialNumber;
}
return Environment.ExpandEnvironmentVariables("/c %ANDROID_HOME%/platform-tools/adb.exe" + SerialNumber + " ");
}
public override void Deploy(ProjectParams Params, DeploymentContext SC)
{
string SOName = GetFinalSOName(Params, SC.StageExecutables[0]);
string AdbCommand = GetAdbCommand(Params);
string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true);
// make sure APK is up to date (this is fast if so)
var Deploy = UEBuildDeploy.GetBuildDeploy(UnrealTargetPlatform.Android);
string CookFlavor = SC.CookPlatform.IndexOf("_") > 0 ? SC.CookPlatform.Substring(SC.CookPlatform.IndexOf("_")) : "";
Deploy.PrepForUATPackageOrDeploy(Params.ShortProjectName, SC.ProjectRoot, SOName, SC.LocalRoot + "/Engine", Params.Distribution, CookFlavor);
// now we can use the apk to get more info
string DeviceObbName = GetDeviceObbName(ApkName);
string PackageName = GetPackageInfo(ApkName, false);
// install the apk
string UninstallCommandline = AdbCommand + "uninstall " + PackageName;
RunAndLog(CmdEnv, CmdEnv.CmdExe, UninstallCommandline);
string InstallCommandline = AdbCommand + "install \"" + ApkName + "\"";
RunAndLog(CmdEnv, CmdEnv.CmdExe, InstallCommandline);
// 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
if (SC.Stage)
{
// cache some strings
string BaseCommandline = AdbCommand + "push";
string RemoteDir = "/mnt/sdcard/" + Params.ShortProjectName;
string UE4GameRemoteDir = "/mnt/sdcard/" + Params.ShortProjectName;
// make sure device is at a clean state
Run(CmdEnv.CmdExe, AdbCommand + "shell rm -r " + RemoteDir);
Run(CmdEnv.CmdExe, AdbCommand + "shell rm -r " + UE4GameRemoteDir);
string[] Files = Directory.GetFiles(SC.StageDirectory, "*", SearchOption.AllDirectories);
// copy each UFS file
foreach (string Filename in Files)
{
// don't push the apk, we install it
if (Path.GetExtension(Filename).Equals(".apk", StringComparison.InvariantCultureIgnoreCase))
{
continue;
}
string FinalRemoteDir = RemoteDir;
/* // handle the special case of the UE4Commandline.txt when using content only game (UE4Game)
if (!Params.IsCodeBasedProject &&
Path.GetFileName(Filename).Equals("UE4CommandLine.txt", StringComparison.InvariantCultureIgnoreCase))
{
FinalRemoteDir = "/mnt/sdcard/UE4Game";
}*/
string RemoteFilename = Filename.Replace(SC.StageDirectory, FinalRemoteDir).Replace("\\", "/");
string Commandline = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, Filename, RemoteFilename);
Run(CmdEnv.CmdExe, Commandline);
}
// delete the .obb file, since it will cause nothing we just deployed to be used
Run(CmdEnv.CmdExe, AdbCommand + "shell rm " + DeviceObbName);
}
else
{
// cache some strings
string BaseCommandline = AdbCommand + "push";
string RemoteDir = "/mnt/sdcard/" + Params.ShortProjectName;
string FinalRemoteDir = RemoteDir;
/* // handle the special case of the UE4Commandline.txt when using content only game (UE4Game)
if (!Params.IsCodeBasedProject)
{
FinalRemoteDir = "/mnt/sdcard/UE4Game";
}*/
string RemoteFilename = IntermediateCmdLineFile.Replace(SC.StageDirectory, FinalRemoteDir).Replace("\\", "/");
string Commandline = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, IntermediateCmdLineFile, RemoteFilename);
Run(CmdEnv.CmdExe, Commandline);
}
}
/** Internal usage for GetPackageName */
private static string PackageLine = null;
/** 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();
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();
}
string ReturnValue = null;
if (PackageLine != null)
{
// the line should look like: package: name='com.epicgames.qagame' versionCode='1' versionName='1.0'
string[] Tokens = PackageLine.Split("'".ToCharArray());
int TokenIndex = bRetrieveVersionCode ? 3 : 1;
if (Tokens.Length >= TokenIndex + 1)
{
ReturnValue = Tokens[TokenIndex];
}
}
return ReturnValue;
}
/** Simple function to pipe output asynchronously */
private static void ParsePackageName(object Sender, DataReceivedEventArgs Event)
{
// DataReceivedEventHandler is fired with a null string when the output stream is closed. We don't want to
// print anything for that event.
if (!String.IsNullOrEmpty(Event.Data))
{
if (PackageLine == null)
{
string Line = Event.Data;
if (Line.StartsWith("package:"))
{
PackageLine = Line;
}
}
}
}
private static string GetAaptPath()
{
// there is a numbered directory in here, hunt it down
string[] Subdirs = Directory.GetDirectories(Environment.ExpandEnvironmentVariables("%ANDROID_HOME%/build-tools/"));
if (Subdirs.Length == 0)
{
throw new AutomationException("Failed to find %ANDROID_HOME%/build-tools subdirectory");
}
// we expect there to be one, so use the first one
return Path.Combine(Subdirs[0], "aapt.exe");
}
public override ProcessResult RunClient(ERunOptions ClientRunFlags, string ClientApp, string ClientCmdLine, ProjectParams Params)
{
string ApkName = ClientApp + GetArchitecture(Params) + ".apk";
if (!File.Exists(ApkName))
{
ApkName = GetFinalApkName(Params, Path.GetFileNameWithoutExtension(ClientApp), true);
}
Console.WriteLine("Apk='{0}', CLientApp='{1}', ExeName='{2}'", ApkName, ClientApp, Params.ProjectGameExeFilename);
// run aapt to get the name of the intent
string PackageName = GetPackageInfo(ApkName, false);
if (PackageName == null)
{
throw new AutomationException("Failed to get package name from " + ClientApp);
}
string AdbCommand = GetAdbCommand(Params);
string CommandLine = "shell am start -n " + PackageName + "/com.epicgames.ue4.GameActivity";
// start the app on device!
ProcessResult ClientProcess = Run(CmdEnv.CmdExe, AdbCommand + CommandLine, null, ClientRunFlags);
return ClientProcess;
}
public override void GetFilesToDeployOrStage(ProjectParams Params, DeploymentContext SC)
{
// 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);
// }
}
/// <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>
public override string GetCookPlatform( bool bDedicatedServer, bool bIsClientOnly, string CookFlavor )
{
if( CookFlavor.Length > 0 )
{
return "Android_" + CookFlavor;
}
else
{
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;
}
public override bool RequiresPak(ProjectParams Params)
{
return Params.Package;
}
#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
public override List<string> GetDebugFileExtentions()
{
return new List<string> { };
}
}