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); // Setup the OBB name and add the storage path (queried from the device) to it string DeviceStorageQueryCommand = GetStorageQueryCommand(); ProcessResult Result = RunAdbCommand(Params, DeviceStorageQueryCommand, null, ERunOptions.AppMustExist); String StorageLocation = Result.Output.Trim(); string DeviceObbName = StorageLocation + "/" + GetDeviceObbName(ApkName); string RemoteDir = StorageLocation + "/UE4Game/" + Params.ShortProjectName; // determine if APK out of date string APKLastUpdateTime = new FileInfo(ApkName).LastWriteTime.ToString(); bool bNeedAPKInstall = true; if (Params.IterativeDeploy) { // Check for apk installed with this package name on the device ProcessResult InstalledResult = RunAdbCommand(Params, "shell pm list packages " + PackageName, null, ERunOptions.AppMustExist); if (InstalledResult.Output.Contains(PackageName)) { // See if apk is up to date on device InstalledResult = RunAdbCommand(Params, "shell cat " + RemoteDir + "/APKFileStamp.txt", null, ERunOptions.AppMustExist); if (InstalledResult.Output.StartsWith("APK: ")) { if (InstalledResult.Output.Substring(5).Trim() == APKLastUpdateTime) { bNeedAPKInstall = false; } // Stop the previously running copy (uninstall/install did this before) InstalledResult = RunAdbCommand(Params, "shell am force-stop " + PackageName, null, ERunOptions.AppMustExist); if (InstalledResult.Output.Contains("Error")) { // force-stop not supported (Android < 3.0) so check if package is actually running // Note: cannot use grep here since it may not be installed on device InstalledResult = RunAdbCommand(Params, "shell ps", null, ERunOptions.AppMustExist); if (InstalledResult.Output.Contains(PackageName)) { // it is actually running so use the slow way to kill it (uninstall and reinstall) bNeedAPKInstall = true; } } } } } // install new APK if needed if (bNeedAPKInstall) { // try uninstalling an old app with the same identifier. int SuccessCode = 0; string UninstallCommandline = "uninstall " + PackageName; RunAndLogAdbCommand(Params, UninstallCommandline, out SuccessCode); // install the apk string InstallCommandline = "install \"" + ApkName + "\""; string InstallOutput = RunAndLogAdbCommand(Params, InstallCommandline, out SuccessCode); int FailureIndex = InstallOutput.IndexOf("Failure"); // adb install doesn't always return an error code on failure, and instead prints "Failure", followed by an error code. if (SuccessCode != 0 || FailureIndex != -1) { string ErrorMessage = String.Format("Installation of apk '{0}' failed", ApkName); if (FailureIndex != -1) { string FailureString = InstallOutput.Substring(FailureIndex + 7).Trim(); if (FailureString != "") { ErrorMessage += ": " + FailureString; } } ErrorReporter.Error(ErrorMessage, (int)ErrorCodes.Error_AppInstallFailed); throw new AutomationException(ErrorMessage); } } // 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 = "push"; HashSet <string> EntriesToDeploy = new HashSet <string>(); if (Params.IterativeDeploy) { // always send UE4CommandLine.txt (it was written above after delta checks applied) EntriesToDeploy.Add(IntermediateCmdLineFile); // Add non UFS files if any to deploy String NonUFSManifestPath = SC.GetNonUFSDeploymentDeltaPath(); if (File.Exists(NonUFSManifestPath)) { string NonUFSFiles = File.ReadAllText(NonUFSManifestPath); foreach (string Filename in NonUFSFiles.Split('\n')) { if (!string.IsNullOrEmpty(Filename) && !string.IsNullOrWhiteSpace(Filename)) { EntriesToDeploy.Add(CombinePaths(SC.StageDirectory, Filename.Trim())); } } } // Add UFS files if any to deploy String UFSManifestPath = SC.GetUFSDeploymentDeltaPath(); if (File.Exists(UFSManifestPath)) { string UFSFiles = File.ReadAllText(UFSManifestPath); foreach (string Filename in UFSFiles.Split('\n')) { if (!string.IsNullOrEmpty(Filename) && !string.IsNullOrWhiteSpace(Filename)) { EntriesToDeploy.Add(CombinePaths(SC.StageDirectory, Filename.Trim())); } } } // For now, if too many files may be better to just push them all if (EntriesToDeploy.Count > 500) { // make sure device is at a clean state RunAdbCommand(Params, "shell rm -r " + RemoteDir); EntriesToDeploy.Clear(); EntriesToDeploy.TrimExcess(); EntriesToDeploy.Add(SC.StageDirectory); } } else { // make sure device is at a clean state RunAdbCommand(Params, "shell rm -r " + RemoteDir); // Copy UFS files.. string[] Files = Directory.GetFiles(SC.StageDirectory, "*", SearchOption.AllDirectories); System.Array.Sort(Files); // Find all the files we exclude from copying. And include // the directories we need to individually copy. HashSet <string> ExcludedFiles = new HashSet <string>(); SortedSet <string> IndividualCopyDirectories = new SortedSet <string>((IComparer <string>) new LongestFirst()); foreach (string Filename in Files) { bool Exclude = false; // Don't push the apk, we install it Exclude |= Path.GetExtension(Filename).Equals(".apk", StringComparison.InvariantCultureIgnoreCase); // For excluded files we add the parent dirs to our // tracking of stuff to individually copy. if (Exclude) { ExcludedFiles.Add(Filename); // We include all directories up to the stage root in having // to individually copy the files. for (string FileDirectory = Path.GetDirectoryName(Filename); !FileDirectory.Equals(SC.StageDirectory); FileDirectory = Path.GetDirectoryName(FileDirectory)) { if (!IndividualCopyDirectories.Contains(FileDirectory)) { IndividualCopyDirectories.Add(FileDirectory); } } if (!IndividualCopyDirectories.Contains(SC.StageDirectory)) { IndividualCopyDirectories.Add(SC.StageDirectory); } } } // The directories are sorted above in "deepest" first. We can // therefore start copying those individual dirs which will // recreate the tree. As the subtrees will get copied at each // possible individual level. foreach (string DirectoryName in IndividualCopyDirectories) { string[] Entries = Directory.GetFileSystemEntries(DirectoryName, "*", SearchOption.TopDirectoryOnly); foreach (string Entry in Entries) { // We avoid excluded files and the individual copy dirs // (the individual copy dirs will get handled as we iterate). if (ExcludedFiles.Contains(Entry) || IndividualCopyDirectories.Contains(Entry)) { continue; } else { EntriesToDeploy.Add(Entry); } } } if (EntriesToDeploy.Count == 0) { EntriesToDeploy.Add(SC.StageDirectory); } } // 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(1); 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 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); } // write new timestamp for APK (do it here since RemoteDir will now exist) if (bNeedAPKInstall) { int SuccessCode = 0; RunAndLogAdbCommand(Params, "shell \"echo 'APK: " + APKLastUpdateTime + "' > " + RemoteDir + "/APKFileStamp.txt\"", out SuccessCode); } }