public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL) { Log("Package {0}", Params.RawProjectPath); // ensure the ue4game binary exists, if applicable string FullExePath = CombinePaths(Path.GetDirectoryName(Params.ProjectGameExeFilename), SC.StageExecutables[0] + (UnrealBuildTool.BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac ? ".stub" : "")); if (!SC.IsCodeBasedProject && !FileExists_NoExceptions(FullExePath)) { Log("Failed to find game binary " + FullExePath); throw new AutomationException(ErrorCodes.Error_MissingExecutable, "Stage Failed. Could not find binary {0}. You may need to build the UE4 project with your target configuration and platform.", FullExePath); } //@TODO: We should be able to use this code on both platforms, when the following issues are sorted: // - Raw executable is unsigned & unstripped (need to investigate adding stripping to IPP) // - IPP needs to be able to codesign a raw directory // - IPP needs to be able to take a .app directory instead of a Payload directory when doing RepackageFromStage (which would probably be renamed) // - Some discrepancy in the loading screen pngs that are getting packaged, which needs to be investigated // - Code here probably needs to be updated to write 0 byte files as 1 byte (difference with IPP, was required at one point when using Ionic.Zip to prevent issues on device, maybe not needed anymore?) if (UnrealBuildTool.BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac) { // copy in all of the artwork and plist var DeployHandler = UEBuildDeploy.GetBuildDeploy(UnrealTargetPlatform.IOS); DeployHandler.PrepForUATPackageOrDeploy(Params.ShortProjectName, Path.GetDirectoryName(Params.RawProjectPath), CombinePaths(Path.GetDirectoryName(Params.ProjectGameExeFilename), SC.StageExecutables[0]), CombinePaths(SC.LocalRoot, "Engine"), Params.Distribution, "", false); // figure out where to pop in the staged files string AppDirectory = string.Format("{0}/Payload/{1}.app", Path.GetDirectoryName(Params.ProjectGameExeFilename), Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename)); // delete the old cookeddata InternalUtils.SafeDeleteDirectory(AppDirectory + "/cookeddata", true); InternalUtils.SafeDeleteFile(AppDirectory + "/ue4commandline.txt", true); if (!Params.IterativeDeploy) { // copy the Staged files to the AppDirectory string[] StagedFiles = Directory.GetFiles(SC.StageDirectory, "*", SearchOption.AllDirectories); foreach (string Filename in StagedFiles) { string DestFilename = Filename.Replace(SC.StageDirectory, AppDirectory); Directory.CreateDirectory(Path.GetDirectoryName(DestFilename)); InternalUtils.SafeCopyFile(Filename, DestFilename, true); } } else { // copy just the root stage directory files string[] StagedFiles = Directory.GetFiles(SC.StageDirectory, "*", SearchOption.TopDirectoryOnly); foreach (string Filename in StagedFiles) { string DestFilename = Filename.Replace(SC.StageDirectory, AppDirectory); Directory.CreateDirectory(Path.GetDirectoryName(DestFilename)); InternalUtils.SafeCopyFile(Filename, DestFilename, true); } } } if (SC.StageTargetConfigurations.Count != 1) { throw new AutomationException("iOS is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations", SC.StageTargetConfigurations.Count); } bCreatedIPA = false; bool bNeedsIPA = false; if (Params.IterativeDeploy) { String NonUFSManifestPath = SC.GetNonUFSDeploymentDeltaPath(); // check to determine if we need to update the IPA if (File.Exists(NonUFSManifestPath)) { string NonUFSFiles = File.ReadAllText(NonUFSManifestPath); string[] Lines = NonUFSFiles.Split('\n'); bNeedsIPA = Lines.Length > 0 && !string.IsNullOrWhiteSpace(Lines[0]); } } if (UnrealBuildTool.BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { var TargetConfiguration = SC.StageTargetConfigurations[0]; var ProjectIPA = MakeIPAFileName(TargetConfiguration, Params); var ProjectStub = Path.GetFullPath(Params.ProjectGameExeFilename); // package a .ipa from the now staged directory var IPPExe = CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/IOS/IPhonePackager.exe"); Log("ProjectName={0}", Params.ShortProjectName); Log("ProjectStub={0}", ProjectStub); Log("ProjectIPA={0}", ProjectIPA); Log("IPPExe={0}", IPPExe); bool cookonthefly = Params.CookOnTheFly || Params.SkipCookOnTheFly; // if we are incremental check to see if we need to even update the IPA if (!Params.IterativeDeploy || !File.Exists(ProjectIPA) || bNeedsIPA) { // delete the .ipa to make sure it was made DeleteFile(ProjectIPA); bCreatedIPA = true; if (RemoteToolChain.bUseRPCUtil) { string IPPArguments = "RepackageFromStage \"" + (Params.IsCodeBasedProject ? Params.RawProjectPath : "Engine") + "\""; IPPArguments += " -config " + TargetConfiguration.ToString(); if (TargetConfiguration == UnrealTargetConfiguration.Shipping) { IPPArguments += " -compress=best"; } // Determine if we should sign bool bNeedToSign = GetCodeSignDesirability(Params); if (!String.IsNullOrEmpty(Params.BundleName)) { // Have to sign when a bundle name is specified bNeedToSign = true; IPPArguments += " -bundlename " + Params.BundleName; } if (bNeedToSign) { IPPArguments += " -sign"; if (Params.Distribution) { IPPArguments += " -distribution"; } } IPPArguments += (cookonthefly ? " -cookonthefly" : ""); IPPArguments += " -stagedir \"" + CombinePaths(Params.BaseStageDirectory, "IOS") + "\""; IPPArguments += " -project \"" + Params.RawProjectPath + "\""; if (Params.IterativeDeploy) { IPPArguments += " -iterate"; } RunAndLog(CmdEnv, IPPExe, IPPArguments); } else { List <string> IPPArguments = new List <string>(); IPPArguments.Add("RepackageFromStage"); IPPArguments.Add(Params.IsCodeBasedProject ? Params.RawProjectPath : "Engine"); IPPArguments.Add("-config"); IPPArguments.Add(TargetConfiguration.ToString()); if (TargetConfiguration == UnrealTargetConfiguration.Shipping) { IPPArguments.Add("-compress=best"); } // Determine if we should sign bool bNeedToSign = GetCodeSignDesirability(Params); if (!String.IsNullOrEmpty(Params.BundleName)) { // Have to sign when a bundle name is specified bNeedToSign = true; IPPArguments.Add("-bundlename"); IPPArguments.Add(Params.BundleName); } if (bNeedToSign) { IPPArguments.Add("-sign"); } if (cookonthefly) { IPPArguments.Add(" -cookonthefly"); } IPPArguments.Add(" -stagedir"); IPPArguments.Add(CombinePaths(Params.BaseStageDirectory, "IOS")); IPPArguments.Add(" -project"); IPPArguments.Add(Params.RawProjectPath); if (Params.IterativeDeploy) { IPPArguments.Add(" -iterate"); } if (RunIPP(IPPArguments.ToArray()) != 0) { throw new AutomationException("IPP Failed"); } } } // verify the .ipa exists if (!FileExists(ProjectIPA)) { throw new AutomationException(ErrorCodes.Error_FailedToCreateIPA, "PACKAGE FAILED - {0} was not created", ProjectIPA); } if (WorkingCL > 0) { // Open files for add or edit var ExtraFilesToCheckin = new List <string> { ProjectIPA }; // check in the .ipa along with everything else UE4Build.AddBuildProductsToChangelist(WorkingCL, ExtraFilesToCheckin); } //@TODO: This automatically deploys after packaging, useful for testing on PC when iterating on IPP //Deploy(Params, SC); } else { // create the ipa string IPAName = CombinePaths(Path.GetDirectoryName(Params.RawProjectPath), "Binaries", "IOS", (Params.Distribution ? "Distro_" : "") + Params.ShortProjectName + (SC.StageTargetConfigurations[0] != UnrealTargetConfiguration.Development ? ("-IOS-" + SC.StageTargetConfigurations[0].ToString()) : "") + ".ipa"); if (!Params.IterativeDeploy || !File.Exists(IPAName) || bNeedsIPA) { bCreatedIPA = true; // code sign the app CodeSign(Path.GetDirectoryName(Params.ProjectGameExeFilename), Params.IsCodeBasedProject ? Params.ShortProjectName : Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename), Params.RawProjectPath, SC.StageTargetConfigurations[0], SC.LocalRoot, Params.ShortProjectName, Path.GetDirectoryName(Params.RawProjectPath), SC.IsCodeBasedProject, Params.Distribution); // now generate the ipa PackageIPA(Path.GetDirectoryName(Params.ProjectGameExeFilename), Params.IsCodeBasedProject ? Params.ShortProjectName : Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename), Params.ShortProjectName, Path.GetDirectoryName(Params.RawProjectPath), SC.StageTargetConfigurations[0], Params.Distribution); } } PrintRunTime(); }
public override void Deploy(ProjectParams Params, DeploymentContext SC) { string DeviceArchitecture = GetBestDeviceArchitecture(Params); string GPUArchitecture = GetBestGPUArchitecture(Params); string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, DeviceArchitecture, GPUArchitecture); // make sure APK is up to date (this is fast if so) var Deploy = UEBuildDeploy.GetBuildDeploy(UnrealTargetPlatform.Android); if (!Params.Prebuilt) { string CookFlavor = SC.FinalCookPlatform.IndexOf("_") > 0 ? SC.FinalCookPlatform.Substring(SC.FinalCookPlatform.IndexOf("_")) : ""; string SOName = GetSONameWithoutArchitecture(Params, SC.StageExecutables[0]); Deploy.PrepForUATPackageOrDeploy(Params.ShortProjectName, SC.ProjectRoot, SOName, SC.LocalRoot + "/Engine", Params.Distribution, CookFlavor, true); } // now we can use the apk to get more info string PackageName = GetPackageInfo(ApkName, false); // 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); } // 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); // 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); // copy files to device if we were staging if (SC.Stage) { // cache some strings string BaseCommandline = "push"; string RemoteDir = StorageLocation + "/UE4Game/" + Params.ShortProjectName; string UE4GameRemoteDir = StorageLocation + "/UE4Game/" + Params.ShortProjectName; // make sure device is at a clean state RunAdbCommand(Params, "shell rm -r " + RemoteDir); RunAdbCommand(Params, "shell rm -r " + UE4GameRemoteDir); // 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. HashSet <string> EntriesToDeploy = new HashSet <string>(); foreach (string DirectoryName in IndividualCopyDirectories) { string[] Entries = Directory.GetFileSystemEntries(DirectoryName, "*", SearchOption.TopDirectoryOnly); foreach (string Entry in Entries) { // We avoid excluded files and the individual copy dirs // (the individual copy dirs will get handled as we iterate). if (ExcludedFiles.Contains(Entry) || IndividualCopyDirectories.Contains(Entry)) { continue; } else { EntriesToDeploy.Add(Entry); } } } if (EntriesToDeploy.Count == 0) { EntriesToDeploy.Add(SC.StageDirectory); } // 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) { string FinalRemoteDir = RemoteDir; string RemotePath = Entry.Replace(SC.StageDirectory, FinalRemoteDir).Replace("\\", "/"); string Commandline = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, Entry, RemotePath); // We run deploy commands in parallel to maximize the connection // throughput. DeployCommands.Add( RunAdbCommand(Params, Commandline, null, ERunOptions.Default | ERunOptions.NoWaitForExit)); // But we limit the parallel commands to avoid overwhelming // memory resources. if (DeployCommands.Count == DeployMaxParallelCommands) { while (DeployCommands.Count > DeployMaxParallelCommands / 2) { Thread.Sleep(10); DeployCommands.RemoveWhere( delegate(ProcessResult r) { return(r.HasExited); }); } } } foreach (ProcessResult deploy_result in DeployCommands) { deploy_result.WaitForExit(); } // delete the .obb file, since it will cause nothing we just deployed to be used RunAdbCommand(Params, "shell rm " + DeviceObbName); } 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 string BaseCommandline = "push"; string Commandline = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, ObbPath, DeviceObbName); RunAdbCommand(Params, Commandline); } } else { // cache some strings string BaseCommandline = "push"; string RemoteDir = StorageLocation + "/UE4Game/" + 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); RunAdbCommand(Params, Commandline); } }
public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL) { string[] Architectures = UnrealBuildTool.AndroidToolChain.GetAllArchitectures(); string[] GPUArchitectures = UnrealBuildTool.AndroidToolChain.GetAllGPUArchitectures(); bool bMakeSeparateApks = UnrealBuildTool.Android.UEDeployAndroid.ShouldMakeSeparateApks(); foreach (string Architecture in Architectures) { foreach (string GPUArchitecture in GPUArchitectures) { string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : ""); string BatchName = GetFinalBatchName(ApkName, Params, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : ""); // 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); if (!Params.Prebuilt) { string CookFlavor = SC.FinalCookPlatform.IndexOf("_") > 0 ? SC.FinalCookPlatform.Substring(SC.FinalCookPlatform.IndexOf("_")) : ""; string SOName = GetSONameWithoutArchitecture(Params, SC.StageExecutables[0]); Deploy.PrepForUATPackageOrDeploy(Params.ShortProjectName, SC.ProjectRoot, SOName, SC.LocalRoot + "/Engine", Params.Distribution, CookFlavor); } // first, look for a .pak file in the staged directory string[] PakFiles = Directory.GetFiles(SC.StageDirectory, "*.pak", SearchOption.AllDirectories); bool bHasPakFile = PakFiles.Length >= 1; // for now, we only support 1 pak/obb file if (PakFiles.Length > 1) { string ErrorString = String.Format("Can't package for Android with 0 or more than 1 pak file (found {0} pak files in {1})", PakFiles.Length, SC.StageDirectory); ErrorReporter.Error(ErrorString, (int)ErrorCodes.Error_OnlyOneObbFileSupported); throw new AutomationException(ErrorString); } 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 && bHasPakFile) { 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", "set DEVICE=", "if not \"%1\"==\"\" set DEVICE=-s %1", "for /f \"delims=\" %%A in ('adb " + GetStorageQueryCommand() + "') do @set STORAGE=%%A", "%ADB% %DEVICE% uninstall " + PackageName, "%ADB% %DEVICE% install " + Path.GetFileName(ApkName), "@if \"%ERRORLEVEL%\" NEQ \"0\" goto Error", "%ADB% %DEVICE% shell rm -r %STORAGE%/" + Params.ShortProjectName, "%ADB% %DEVICE% shell rm -r %STORAGE%/UE4Game/UE4CommandLine.txt", // we need to delete the commandline in UE4Game or it will mess up loading "%ADB% %DEVICE% shell rm -r %STORAGE%/obb/" + PackageName, Params.OBBinAPK || !bHasPakFile ? "" : "%ADB% %DEVICE% push " + Path.GetFileName(LocalObbName) + " %STORAGE%/" + DeviceObbName, Params.OBBinAPK || !bHasPakFile ? "" : "if \"%ERRORLEVEL%\" NEQ \"0\" goto Error", "goto:eof", ":Error", "@echo.", "@echo There was an error installing the game or the obb file. Look above for more info.", "@echo.", "@echo Things to try:", "@echo Check that the device (and only the device) is listed with \"%ADB$ devices\" from a command prompt.", "@echo Make sure all Developer options look normal on the device", "@echo Check that the device has an SD card.", "@pause" }; File.WriteAllLines(BatchName, BatchLines); } } PrintRunTime(); }
public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL) { string[] Architectures = UnrealBuildTool.AndroidToolChain.GetAllArchitectures(); string[] GPUArchitectures = UnrealBuildTool.AndroidToolChain.GetAllGPUArchitectures(); bool bMakeSeparateApks = UnrealBuildTool.Android.UEDeployAndroid.ShouldMakeSeparateApks(); bool bPackageDataInsideApk = UnrealBuildTool.Android.UEDeployAndroid.PackageDataInsideApk(false); 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(); } foreach (string Architecture in Architectures) { foreach (string GPUArchitecture in GPUArchitectures) { string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : ""); if (!SC.IsCodeBasedProject) { string UE4GameApkName = GetFinalApkName(Params, SC.StageExecutables[0], false, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : ""); if (FileExists_NoExceptions(UE4GameApkName) == false) { Log("Failed to find game apk " + UE4GameApkName); AutomationTool.ErrorReporter.Error("Stage Failed.", (int)AutomationTool.ErrorCodes.Error_MissingExecutable); throw new AutomationException("Could not find apk {0}. You may need to build the UE4 project with your target configuration and platform.", UE4GameApkName); } } string BatchName = GetFinalBatchName(ApkName, Params, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : ""); if (!Params.Prebuilt) { string CookFlavor = SC.FinalCookPlatform.IndexOf("_") > 0 ? SC.FinalCookPlatform.Substring(SC.FinalCookPlatform.IndexOf("_")) : ""; string SOName = GetSONameWithoutArchitecture(Params, SC.StageExecutables[0]); Deploy.PrepForUATPackageOrDeploy(Params.ShortProjectName, SC.ProjectRoot, SOName, SC.LocalRoot + "/Engine", Params.Distribution, CookFlavor, false); } // Create APK specific OBB in case we have a detached OBB. string DeviceObbName = ""; string ObbName = ""; if (!bPackageDataInsideApk) { DeviceObbName = GetDeviceObbName(ApkName); ObbName = GetFinalObbName(ApkName); CopyFile(LocalObbName, ObbName); } // Write install batch file(s). string PackageName = GetPackageInfo(ApkName, false); // make a batch file that can be used to install the .apk and .obb files string[] BatchLines; if (Utils.IsRunningOnMono) { Log("Writing shell script for install with {0}", bPackageDataInsideApk ? "data in APK" : "separate obb"); BatchLines = new string[] { "#!/bin/sh", "cd \"`dirname \"$0\"`\"", "ADB=", "if [ \"$ANDROID_HOME\" != \"\" ]; then ADB=$ANDROID_HOME/platform-tools/adb; else ADB=" + Environment.GetEnvironmentVariable("ANDROID_HOME") + "; fi", "DEVICE=", "if [ \"$1\" != \"\" ]; then DEVICE=\"-s $1\"; fi", "echo", "echo Uninstalling existing application. Failures here can almost always be ignored.", "$ADB $DEVICE uninstall " + PackageName, "echo", "echo Installing existing application. Failures here indicate a problem with the device \\(connection or storage permissions\\) and are fatal.", "$ADB $DEVICE install " + Path.GetFileName(ApkName), "if [ $? -eq 0 ]; then", "\techo", "\techo Removing old data. Failures here are usually fine - indicating the files were not on the device.", "\t$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/UE4Game/" + Params.ShortProjectName + "'", "\t$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/UE4Game/UE4CommandLine.txt" + "'", "\t$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/" + TargetAndroidLocation + PackageName + "'", bPackageDataInsideApk ? "" : "\techo", bPackageDataInsideApk ? "" : "\techo Installing new data. Failures here indicate storage problems \\(missing SD card or bad permissions\\) and are fatal.", bPackageDataInsideApk ? "" : "\tSTORAGE=$(echo \"`$ADB $DEVICE shell 'echo $EXTERNAL_STORAGE'`\" | cat -v | tr -d '^M')", bPackageDataInsideApk ? "" : "\t$ADB $DEVICE push " + Path.GetFileName(ObbName) + " $STORAGE/" + DeviceObbName, bPackageDataInsideApk ? "if [ 1 ]; then" : "\tif [ $? -eq 0 ]; then", "\t\techo", "\t\techo Installation successful", "\t\texit 0", "\tfi", "fi", "echo", "echo There was an error installing the game or the obb file. Look above for more info.", "echo", "echo Things to try:", "echo Check that the device (and only the device) is listed with \\\"$ADB devices\\\" from a command prompt.", "echo Make sure all Developer options look normal on the device", "echo Check that the device has an SD card.", "exit 1" }; } else { Log("Writing bat for install with {0}", bPackageDataInsideApk ? "data in APK" : "separate OBB"); BatchLines = new string[] { "setlocal", "set ANDROIDHOME=%ANDROID_HOME%", "if \"%ANDROIDHOME%\"==\"\" set ANDROIDHOME=" + Environment.GetEnvironmentVariable("ANDROID_HOME"), "set ADB=%ANDROIDHOME%\\platform-tools\\adb.exe", "set DEVICE=", "if not \"%1\"==\"\" set DEVICE=-s %1", "for /f \"delims=\" %%A in ('%ADB% %DEVICE% " + GetStorageQueryCommand() + "') do @set STORAGE=%%A", "@echo.", "@echo Uninstalling existing application. Failures here can almost always be ignored.", "%ADB% %DEVICE% uninstall " + PackageName, "@echo.", "@echo Installing existing application. Failures here indicate a problem with the device (connection or storage permissions) and are fatal.", "%ADB% %DEVICE% install " + Path.GetFileName(ApkName), "@if \"%ERRORLEVEL%\" NEQ \"0\" goto Error", "%ADB% %DEVICE% shell rm -r %STORAGE%/UE4Game/" + Params.ShortProjectName, "%ADB% %DEVICE% shell rm -r %STORAGE%/UE4Game/UE4CommandLine.txt", // we need to delete the commandline in UE4Game or it will mess up loading "%ADB% %DEVICE% shell rm -r %STORAGE%/" + TargetAndroidLocation + PackageName, bPackageDataInsideApk ? "" : "@echo.", bPackageDataInsideApk ? "" : "@echo Installing new data. Failures here indicate storage problems (missing SD card or bad permissions) and are fatal.", bPackageDataInsideApk ? "" : "%ADB% %DEVICE% push " + Path.GetFileName(ObbName) + " %STORAGE%/" + DeviceObbName, bPackageDataInsideApk ? "" : "if \"%ERRORLEVEL%\" NEQ \"0\" goto Error", "@echo.", "@echo Installation successful", "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" }; } File.WriteAllLines(BatchName, BatchLines); if (Utils.IsRunningOnMono) { CommandUtils.FixUnixFilePermissions(BatchName); } } } PrintRunTime(); }
public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL) { var Architectures = UnrealBuildTool.AndroidToolChain.GetAllArchitectures(); var GPUArchitectures = UnrealBuildTool.AndroidToolChain.GetAllGPUArchitectures(); bool bMakeSeparateApks = UnrealBuildTool.Android.UEDeployAndroid.ShouldMakeSeparateApks(); bool bPackageDataInsideApk = UnrealBuildTool.Android.UEDeployAndroid.PackageDataInsideApk(false); 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(); } foreach (string Architecture in Architectures) { foreach (string GPUArchitecture in GPUArchitectures) { string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : ""); if (!SC.IsCodeBasedProject) { string UE4SOName = GetFinalApkName(Params, SC.StageExecutables[0], false, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : ""); UE4SOName = UE4SOName.Replace(".apk", ".so"); if (FileExists_NoExceptions(UE4SOName) == false) { Log("Failed to find game .so " + UE4SOName); throw new AutomationException(ErrorCodes.Error_MissingExecutable, "Stage Failed. Could not find .so {0}. You may need to build the UE4 project with your target configuration and platform.", UE4SOName); } } if (!Params.Prebuilt) { string CookFlavor = SC.FinalCookPlatform.IndexOf("_") > 0 ? SC.FinalCookPlatform.Substring(SC.FinalCookPlatform.IndexOf("_")) : ""; string SOName = GetSONameWithoutArchitecture(Params, SC.StageExecutables[0]); Deploy.PrepForUATPackageOrDeploy(Params.ShortProjectName, SC.ProjectRoot, SOName, SC.LocalRoot + "/Engine", Params.Distribution, CookFlavor, false); } // Create APK specific OBB in case we have a detached OBB. string DeviceObbName = ""; string ObbName = ""; if (!bPackageDataInsideApk) { DeviceObbName = GetDeviceObbName(ApkName); ObbName = GetFinalObbName(ApkName); CopyFile(LocalObbName, ObbName); } // Write install batch file(s). string BatchName = GetFinalBatchName(ApkName, Params, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "", false); string PackageName = GetPackageInfo(ApkName, false); // make a batch file that can be used to install the .apk and .obb files string[] BatchLines = GenerateInstallBatchFile(bPackageDataInsideApk, PackageName, ApkName, Params, ObbName, DeviceObbName, false); File.WriteAllLines(BatchName, BatchLines); // If we aren't packaging data in the APK then lets write out a bat file to also let us test without the OBB // on the device. String NoInstallBatchName = GetFinalBatchName(ApkName, Params, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "", true); // if(!bPackageDataInsideApk) { BatchLines = GenerateInstallBatchFile(bPackageDataInsideApk, PackageName, ApkName, Params, ObbName, DeviceObbName, true); File.WriteAllLines(NoInstallBatchName, BatchLines); } if (Utils.IsRunningOnMono) { CommandUtils.FixUnixFilePermissions(BatchName); if (File.Exists(NoInstallBatchName)) { CommandUtils.FixUnixFilePermissions(NoInstallBatchName); } } } } PrintRunTime(); }