public override List <FileReference> FinalizeBinaryPaths(FileReference BinaryName, FileReference ProjectFile, ReadOnlyTargetRules Target)
        {
            AndroidToolChain ToolChain = CreateToolChain(Target) as AndroidToolChain;

            List <string> Architectures    = ToolChain.GetAllArchitectures();
            List <string> GPUArchitectures = ToolChain.GetAllGPUArchitectures();

            // make multiple output binaries
            List <FileReference> AllBinaries = new List <FileReference>();

            foreach (string Architecture in Architectures)
            {
                foreach (string GPUArchitecture in GPUArchitectures)
                {
                    string BinaryPath;
                    if (Target.bShouldCompileAsDLL)
                    {
                        BinaryPath = Path.Combine(BinaryName.Directory.FullName, Target.Configuration.ToString(), "libUE4.so");
                    }
                    else
                    {
                        BinaryPath = AndroidToolChain.InlineArchName(BinaryName.FullName, Architecture, GPUArchitecture);
                    }

                    AllBinaries.Add(new FileReference(BinaryPath));
                }
            }

            return(AllBinaries);
        }
Пример #2
0
        public override List <string> FinalizeBinaryPaths(string BinaryName)
        {
            string[] Architectures    = AndroidToolChain.GetAllArchitectures();
            string[] GPUArchitectures = AndroidToolChain.GetAllGPUArchitectures();

            // make multiple output binaries
            List <string> AllBinaries = new List <string>();

            foreach (string Architecture in Architectures)
            {
                foreach (string GPUArchitecture in GPUArchitectures)
                {
                    AllBinaries.Add(AndroidToolChain.InlineArchName(BinaryName, Architecture, GPUArchitecture));
                }
            }

            return(AllBinaries);
        }
        public override List <FileReference> FinalizeBinaryPaths(FileReference BinaryName, FileReference ProjectFile, ReadOnlyTargetRules Target)
        {
            AndroidToolChain ToolChain = new AndroidToolChain(ProjectFile, false, Target.AndroidPlatform.Architectures, Target.AndroidPlatform.GPUArchitectures);

            List <string> Architectures    = ToolChain.GetAllArchitectures();
            List <string> GPUArchitectures = ToolChain.GetAllGPUArchitectures();

            // make multiple output binaries
            List <FileReference> AllBinaries = new List <FileReference>();

            foreach (string Architecture in Architectures)
            {
                foreach (string GPUArchitecture in GPUArchitectures)
                {
                    AllBinaries.Add(new FileReference(AndroidToolChain.InlineArchName(BinaryName.FullName, Architecture, GPUArchitecture)));
                }
            }

            return(AllBinaries);
        }
Пример #4
0
        public override List <FileReference> FinalizeBinaryPaths(FileReference BinaryName, FileReference ProjectFile)
        {
            AndroidToolChain ToolChain = new AndroidToolChain(ProjectFile);

            var Architectures    = ToolChain.GetAllArchitectures();
            var GPUArchitectures = ToolChain.GetAllGPUArchitectures();

            // make multiple output binaries
            List <FileReference> AllBinaries = new List <FileReference>();

            foreach (string Architecture in Architectures)
            {
                foreach (string GPUArchitecture in GPUArchitectures)
                {
                    AllBinaries.Add(new FileReference(AndroidToolChain.InlineArchName(BinaryName.FullName, Architecture, GPUArchitecture)));
                }
            }

            return(AllBinaries);
        }
Пример #5
0
        public override List <FileReference> FinalizeBinaryPaths(FileReference BinaryName, FileReference ProjectFile, ReadOnlyTargetRules Target)
        {
            // the CppPlatform here doesn't actually matter, so this will work even for sub-platforms
            AndroidToolChain ToolChain = CreateToolChain(CppPlatform.Android, Target) as AndroidToolChain;

            List <string> Architectures    = ToolChain.GetAllArchitectures();
            List <string> GPUArchitectures = ToolChain.GetAllGPUArchitectures();

            // make multiple output binaries
            List <FileReference> AllBinaries = new List <FileReference>();

            foreach (string Architecture in Architectures)
            {
                foreach (string GPUArchitecture in GPUArchitectures)
                {
                    AllBinaries.Add(new FileReference(AndroidToolChain.InlineArchName(BinaryName.FullName, Architecture, GPUArchitecture)));
                }
            }

            return(AllBinaries);
        }
		public override List<FileReference> FinalizeBinaryPaths(FileReference BinaryName, FileReference ProjectFile)
		{
			AndroidToolChain ToolChain = new AndroidToolChain(ProjectFile);

			var Architectures = ToolChain.GetAllArchitectures();
			var GPUArchitectures = ToolChain.GetAllGPUArchitectures();

			// make multiple output binaries
			List<FileReference> AllBinaries = new List<FileReference>();
			foreach (string Architecture in Architectures)
			{
				foreach (string GPUArchitecture in GPUArchitectures)
				{
					AllBinaries.Add(new FileReference(AndroidToolChain.InlineArchName(BinaryName.FullName, Architecture, GPUArchitecture)));
				}
			}

			return AllBinaries;
		}
		private bool CheckDependencies(AndroidToolChain ToolChain, string ProjectName, string ProjectDirectory, string UE4BuildFilesPath, string GameBuildFilesPath, string EngineDirectory, List<string> SettingsFiles,
			string CookFlavor, string OutputPath, string UE4BuildPath, bool bMakeSeparateApks, bool bPackageDataInsideApk)
		{
			var Arches = ToolChain.GetAllArchitectures();
			var GPUArchitectures = ToolChain.GetAllGPUArchitectures();

			// check all input files (.so, java files, .ini files, etc)
			bool bAllInputsCurrent = true;
			foreach (string Arch in Arches)
			{
				foreach (string GPUArch in GPUArchitectures)
				{
					string SourceSOName = AndroidToolChain.InlineArchName(OutputPath, Arch, GPUArch);
					// if the source binary was UE4Game, replace it with the new project name, when re-packaging a binary only build
					string ApkFilename = Path.GetFileNameWithoutExtension(OutputPath).Replace("UE4Game", ProjectName);
					string DestApkName = Path.Combine(ProjectDirectory, "Binaries/Android/") + ApkFilename + ".apk";

					// if we making multiple Apks, we need to put the architecture into the name
					if (bMakeSeparateApks)
					{
						DestApkName = AndroidToolChain.InlineArchName(DestApkName, Arch, GPUArch);
					}

					// 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));
					}

					// make sure changed java files will rebuild apk
					InputFiles.AddRange(SettingsFiles);

					// rebuild if .pak files exist for OBB in APK case
					if (bPackageDataInsideApk)
					{
						string PAKFileLocation = ProjectDirectory + "/Saved/StagedBuilds/Android" + CookFlavor + "/" + ProjectName + "/Content/Paks";
						if (Directory.Exists(PAKFileLocation))
						{
							var PakFiles = Directory.EnumerateFiles(PAKFileLocation, "*.pak", SearchOption.TopDirectoryOnly);
							foreach (var Name in PakFiles)
							{
								InputFiles.Add(Name);
							}
						}
					}

					// look for any newer input file
					DateTime ApkTime = File.GetLastWriteTimeUtc(DestApkName);
					foreach (var InputFileName in InputFiles)
					{
						if (File.Exists(InputFileName))
						{
							// skip .log files
							if (Path.GetExtension(InputFileName) == ".log")
							{
								continue;
							}
							DateTime InputFileTime = File.GetLastWriteTimeUtc(InputFileName);
							if (InputFileTime.CompareTo(ApkTime) > 0)
							{
								bAllInputsCurrent = false;
								Log.TraceInformation("{0} is out of date due to newer input file {1}", DestApkName, InputFileName);
								break;
							}
						}
					}
				}
			}

			return bAllInputsCurrent;
		}
		private string GetAllBuildSettings(AndroidToolChain ToolChain, string BuildPath, bool bForDistribution, bool bMakeSeparateApks, bool bPackageDataInsideApk, bool bDisableVerifyOBBOnStartUp)
		{
			// make the settings string - this will be char by char compared against last time
			StringBuilder CurrentSettings = new StringBuilder();
			CurrentSettings.AppendLine(string.Format("NDKROOT={0}", Environment.GetEnvironmentVariable("NDKROOT")));
			CurrentSettings.AppendLine(string.Format("ANDROID_HOME={0}", Environment.GetEnvironmentVariable("ANDROID_HOME")));
			CurrentSettings.AppendLine(string.Format("ANT_HOME={0}", Environment.GetEnvironmentVariable("ANT_HOME")));
			CurrentSettings.AppendLine(string.Format("JAVA_HOME={0}", Environment.GetEnvironmentVariable("JAVA_HOME")));
			CurrentSettings.AppendLine(string.Format("SDKVersion={0}", GetSdkApiLevel(ToolChain)));
			CurrentSettings.AppendLine(string.Format("bForDistribution={0}", bForDistribution));
			CurrentSettings.AppendLine(string.Format("bMakeSeparateApks={0}", bMakeSeparateApks));
			CurrentSettings.AppendLine(string.Format("bPackageDataInsideApk={0}", bPackageDataInsideApk));
			CurrentSettings.AppendLine(string.Format("bDisableVerifyOBBOnStartUp={0}", bDisableVerifyOBBOnStartUp));

			// all AndroidRuntimeSettings ini settings in here
			ConfigCacheIni Ini = GetConfigCacheIni("Engine");
			ConfigCacheIni.IniSection Section = Ini.FindSection("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings");
			if (Section != null)
			{
				foreach (string Key in Section.Keys)
				{
					List<string> Values = Section[Key];
					foreach (string Value in Values)
					{
						CurrentSettings.AppendLine(string.Format("{0}={1}", Key, Value));
					}
				}
			}

			Section = Ini.FindSection("/Script/AndroidPlatformEditor.AndroidSDKSettings");
			if (Section != null)
			{
				foreach (string Key in Section.Keys)
				{
					List<string> Values = Section[Key];
					foreach (string Value in Values)
					{
						CurrentSettings.AppendLine(string.Format("{0}={1}", Key, Value));
					}
				}
			}

			var Arches = ToolChain.GetAllArchitectures();
			foreach (string Arch in Arches)
			{
				CurrentSettings.AppendFormat("Arch={0}{1}", Arch, Environment.NewLine);
			}

			var GPUArchitectures = ToolChain.GetAllGPUArchitectures();
			foreach (string GPUArch in GPUArchitectures)
			{
				CurrentSettings.AppendFormat("GPUArch={0}{1}", GPUArch, Environment.NewLine);
			}

			return CurrentSettings.ToString();
		}
		public override bool PrepTargetForDeployment(UEBuildTarget InTarget)
		{
			//Log.TraceInformation("$$$$$$$$$$$$$$ PrepTargetForDeployment $$$$$$$$$$$$$$$$$");
			AndroidToolChain ToolChain = new AndroidToolChain(InTarget.ProjectFile); 

			// we need to strip architecture from any of the output paths
			string BaseSoName = ToolChain.RemoveArchName(InTarget.OutputPaths[0].FullName);

			// get the receipt
			UnrealTargetPlatform Platform = InTarget.Platform;
			UnrealTargetConfiguration Configuration = InTarget.Configuration;
			string ProjectBaseName = Path.GetFileName(BaseSoName).Replace("-" + Platform, "").Replace("-" + Configuration, "").Replace(".so", "");
			string ReceiptFilename = TargetReceipt.GetDefaultPath(InTarget.ProjectDirectory.FullName, ProjectBaseName, Platform, Configuration, "");
			Log.TraceInformation("Receipt Filename: {0}", ReceiptFilename);
			SetAndroidPluginData(ToolChain.GetAllArchitectures(), CollectPluginDataPaths(TargetReceipt.Read(ReceiptFilename)));

			// make an apk at the end of compiling, so that we can run without packaging (debugger, cook on the fly, etc)
			MakeApk(ToolChain, InTarget.AppName, InTarget.ProjectDirectory.FullName, BaseSoName, BuildConfiguration.RelativeEnginePath, bForDistribution: false, CookFlavor: "",
				bMakeSeparateApks: ShouldMakeSeparateApks(), bIncrementalPackage: true, bDisallowPackagingDataInApk: false);

			// if we made any non-standard .apk files, the generated debugger settings may be wrong
			if (ShouldMakeSeparateApks() && (InTarget.OutputPaths.Count > 1 || !InTarget.OutputPaths[0].FullName.Contains("-armv7-es2")))
			{
				Console.WriteLine("================================================================================================================================");
				Console.WriteLine("Non-default apk(s) have been made: If you are debugging, you will need to manually select one to run in the debugger properties!");
				Console.WriteLine("================================================================================================================================");
			}
			return true;
		}
		private void MakeApk(AndroidToolChain ToolChain, string ProjectName, string ProjectDirectory, string OutputPath, string EngineDirectory, bool bForDistribution, string CookFlavor, bool bMakeSeparateApks, bool bIncrementalPackage, bool bDisallowPackagingDataInApk)
		{
			Log.TraceInformation("\n===={0}====PREPARING TO MAKE APK=================================================================", DateTime.Now.ToString());

			// cache some tools paths
			string NDKBuildPath = Environment.ExpandEnvironmentVariables("%NDKROOT%/ndk-build" + (Utils.IsRunningOnMono ? "" : ".cmd"));

			// set up some directory info
			string IntermediateAndroidPath = Path.Combine(ProjectDirectory, "Intermediate/Android/");
			string UE4BuildPath = Path.Combine(IntermediateAndroidPath, "APK");
			string UE4JavaFilePath = Path.Combine(ProjectDirectory, "Build", "Android", GetUE4JavaSrcPath());
			string UE4BuildFilesPath = GetUE4BuildFilePath(EngineDirectory);
			string GameBuildFilesPath = Path.Combine(ProjectDirectory, "Build/Android");

			// Generate Java files
			string PackageName = GetPackageName(ProjectName);
			string TemplateDestinationBase = Path.Combine(ProjectDirectory, "Build", "Android", "src", PackageName.Replace('.', Path.DirectorySeparatorChar));
			MakeDirectoryIfRequired(TemplateDestinationBase);

			// We'll be writing the OBB data into the same location as the download service files
			string UE4OBBDataFileName = GetUE4JavaOBBDataFileName(TemplateDestinationBase);
			string UE4DownloadShimFileName = GetUE4JavaDownloadShimFileName(UE4JavaFilePath);

			// Template generated files           
			string JavaTemplateSourceDir = GetUE4TemplateJavaSourceDir(EngineDirectory);
			var templates = from template in Directory.EnumerateFiles(JavaTemplateSourceDir, "*.template")
							let RealName = Path.GetFileNameWithoutExtension(template)
							select new TemplateFile { SourceFile = template, DestinationFile = GetUE4TemplateJavaDestination(TemplateDestinationBase, RealName) };

			// Generate the OBB and Shim files here
			string ObbFileLocation = ProjectDirectory + "/Saved/StagedBuilds/Android" + CookFlavor + ".obb";

			// This is kind of a small hack to get around a rewrite problem
			// We need to make sure the file is there but if the OBB file doesn't exist then we don't want to replace it
			if (File.Exists(ObbFileLocation) || !File.Exists(UE4OBBDataFileName))
			{
				WriteJavaOBBDataFile(UE4OBBDataFileName, PackageName, new List<string> { ObbFileLocation });
			}

			// Make sure any existing proguard file in project is NOT used (back it up)
			string ProjectBuildProguardFile = Path.Combine(GameBuildFilesPath, "proguard-project.txt");
			if (File.Exists(ProjectBuildProguardFile))
			{
				string ProjectBackupProguardFile = Path.Combine(GameBuildFilesPath, "proguard-project.backup");
				File.Move(ProjectBuildProguardFile, ProjectBackupProguardFile);
			}

			WriteJavaDownloadSupportFiles(UE4DownloadShimFileName, templates, new Dictionary<string, string>{
                { "$$GameName$$", ProjectName },
                { "$$PublicKey$$", GetPublicKey() }, 
                { "$$PackageName$$",PackageName }
            });

			// Sometimes old files get left behind if things change, so we'll do a clean up pass
			{
				string CleanUpBaseDir = Path.Combine(ProjectDirectory, "Build", "Android", "src");
				var files = Directory.EnumerateFiles(CleanUpBaseDir, "*.java", SearchOption.AllDirectories);

				Log.TraceInformation("Cleaning up files based on template dir {0}", TemplateDestinationBase);

				// Make a set of files that are okay to clean up
				var cleanFiles = new HashSet<string>();
				cleanFiles.Add("OBBData.java");
				foreach (var template in templates)
				{
					cleanFiles.Add(Path.GetFileName(template.DestinationFile));
				}

				foreach (var filename in files)
				{
					if (filename == UE4DownloadShimFileName)  // we always need the shim, and it'll get rewritten if needed anyway
						continue;

					string filePath = Path.GetDirectoryName(filename);  // grab the file's path
					if (filePath != TemplateDestinationBase)             // and check to make sure it isn't the same as the Template directory we calculated earlier
					{
						// Only delete the files in the cleanup set
						if (!cleanFiles.Contains(Path.GetFileName(filename)))
							continue;

						Log.TraceInformation("Cleaning up file {0} with path {1}", filename, filePath);
						File.Delete(filename);

						// Check to see if this file also exists in our target destination, and if so nuke it too
						string DestFilename = Path.Combine(UE4BuildPath, Utils.MakePathRelativeTo(filePath, UE4BuildFilesPath));
						if (File.Exists(filename))
						{
							File.Delete(filename);
						}
					}
				}

				// Directory clean up code
				var directories = Directory.EnumerateDirectories(CleanUpBaseDir, "*", SearchOption.AllDirectories).OrderByDescending(x => x);
				foreach (var directory in directories)
				{
					if (Directory.Exists(directory) && Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories).Count() == 0)
					{
						Log.TraceInformation("Cleaning Directory {0} as empty.", directory);
						Directory.Delete(directory, true);
					}
				};


			}


			// cache if we want data in the Apk
			bool bPackageDataInsideApk = PackageDataInsideApk(bDisallowPackagingDataInApk);
			bool bDisableVerifyOBBOnStartUp = DisableVerifyOBBOnStartUp();

			// check to see if any "meta information" is newer than last time we build
			string CurrentBuildSettings = GetAllBuildSettings(ToolChain, UE4BuildPath, bForDistribution, bMakeSeparateApks, bPackageDataInsideApk, bDisableVerifyOBBOnStartUp);
			string BuildSettingsCacheFile = Path.Combine(UE4BuildPath, "UEBuildSettings.txt");

			// do we match previous build settings?
			bool bBuildSettingsMatch = true;

			// get application name and whether it changed, needing to force repackage
			string ApplicationDisplayName;
			if (CheckApplicationName(UE4BuildPath, ProjectName, out ApplicationDisplayName))
			{
				bBuildSettingsMatch = false;
				Log.TraceInformation("Application display name is different than last build, forcing repackage.");
			}

			// if the manifest matches, look at other settings stored in a file
			if (bBuildSettingsMatch)
			{
				if (File.Exists(BuildSettingsCacheFile))
				{
					string PreviousBuildSettings = File.ReadAllText(BuildSettingsCacheFile);
					if (PreviousBuildSettings != CurrentBuildSettings)
					{
						bBuildSettingsMatch = false;
						Log.TraceInformation("Previous .apk file(s) were made with different build settings, forcing repackage.");
					}
				}
			}

			// only check input dependencies if the build settings already match
			if (bBuildSettingsMatch)
			{
				// check if so's are up to date against various inputs
				var JavaFiles = new List<string>{
                                                    UE4OBBDataFileName,
                                                    UE4DownloadShimFileName
                                                };
				// Add the generated files too
				JavaFiles.AddRange(from t in templates select t.SourceFile);
				JavaFiles.AddRange(from t in templates select t.DestinationFile);

				bBuildSettingsMatch = CheckDependencies(ToolChain, ProjectName, ProjectDirectory, UE4BuildFilesPath, GameBuildFilesPath,
					EngineDirectory, JavaFiles, CookFlavor, OutputPath, UE4BuildPath, bMakeSeparateApks, bPackageDataInsideApk);

			}

			var Arches = ToolChain.GetAllArchitectures();
			var GPUArchitectures = ToolChain.GetAllGPUArchitectures();

			// Initialize APL contexts for each architecture enabled
			List<string> NDKArches = new List<string>();
			foreach (var Arch in Arches)
			{
				string NDKArch = GetNDKArch(Arch);
				if (!NDKArches.Contains(NDKArch))
				{
					NDKArches.Add(NDKArch);
				}
			}
			UPL.Init(NDKArches, bForDistribution, EngineDirectory, UE4BuildPath);

			IEnumerable<Tuple<string, string, string>> BuildList = null;

			if (!bBuildSettingsMatch)
			{
				BuildList = from Arch in Arches
							from GPUArch in GPUArchitectures
							let manifest = GenerateManifest(ToolChain, ProjectName, bForDistribution, bPackageDataInsideApk, GameBuildFilesPath, RequiresOBB(bDisallowPackagingDataInApk, ObbFileLocation), bDisableVerifyOBBOnStartUp, Arch, GPUArch, CookFlavor)
							select Tuple.Create(Arch, GPUArch, manifest);
			}
			else
			{
				BuildList = from Arch in Arches
							from GPUArch in GPUArchitectures
							let manifestFile = Path.Combine(IntermediateAndroidPath, Arch + "_" + GPUArch + "_AndroidManifest.xml")
							let manifest = GenerateManifest(ToolChain, ProjectName, bForDistribution, bPackageDataInsideApk, GameBuildFilesPath, RequiresOBB(bDisallowPackagingDataInApk, ObbFileLocation), bDisableVerifyOBBOnStartUp, Arch, GPUArch, CookFlavor)
							let OldManifest = File.Exists(manifestFile) ? File.ReadAllText(manifestFile) : ""
							where manifest != OldManifest
							select Tuple.Create(Arch, GPUArch, manifest);
			}

			// Now we have to spin over all the arch/gpu combinations to make sure they all match
			int BuildListComboTotal = BuildList.Count();
			if (BuildListComboTotal == 0)
			{
				Log.TraceInformation("Output .apk file(s) are up to date (dependencies and build settings are up to date)");
				return;
			}


			// Once for all arches code:

			// make up a dictionary of strings to replace in xml files (strings.xml)
			Dictionary<string, string> Replacements = new Dictionary<string, string>();
			Replacements.Add("${EXECUTABLE_NAME}", ApplicationDisplayName);

			if (!bIncrementalPackage)
			{
				// Wipe the Intermediate/Build/APK directory first, except for dexedLibs, because Google Services takes FOREVER to predex, and it almost never changes
				// so allow the ANT checking to win here - if this grows a bit with extra libs, it's fine, it _should_ only pull in dexedLibs it needs
				Log.TraceInformation("Performing complete package - wiping {0}, except for predexedLibs", UE4BuildPath);
				DeleteDirectory(UE4BuildPath, "dexedLibs");
			}

			// If we are packaging for Amazon then we need to copy the  file to the correct location
			Log.TraceInformation("bPackageDataInsideApk = {0}", bPackageDataInsideApk);
			if (bPackageDataInsideApk)
			{
				Console.WriteLine("Obb location {0}", ObbFileLocation);
				string ObbFileDestination = UE4BuildPath + "/assets";
				Console.WriteLine("Obb destination location {0}", ObbFileDestination);
				if (File.Exists(ObbFileLocation))
				{
					Directory.CreateDirectory(UE4BuildPath);
					Directory.CreateDirectory(ObbFileDestination);
					Console.WriteLine("Obb file exists...");
					var DestFileName = Path.Combine(ObbFileDestination, "main.obb.png"); // Need a rename to turn off compression
					var SrcFileName = ObbFileLocation;
					if (!File.Exists(DestFileName) || File.GetLastWriteTimeUtc(DestFileName) < File.GetLastWriteTimeUtc(SrcFileName))
					{
						Console.WriteLine("Copying {0} to {1}", SrcFileName, DestFileName);
						File.Copy(SrcFileName, DestFileName);
					}
				}
			}
			else // try to remove the file it we aren't packaging inside the APK
			{
				string ObbFileDestination = UE4BuildPath + "/assets";
				var DestFileName = Path.Combine(ObbFileDestination, "main.obb.png");
				if (File.Exists(DestFileName))
				{
					File.Delete(DestFileName);
				}
			}

			//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 + "/NotForLicensees", UE4BuildPath, Replacements);
			CopyFileDirectory(UE4BuildFilesPath + "/NoRedist", UE4BuildPath, Replacements);
			CopyFileDirectory(GameBuildFilesPath, UE4BuildPath, Replacements);
			CopyFileDirectory(GameBuildFilesPath + "/NotForLicensees", UE4BuildPath, Replacements);
			CopyFileDirectory(GameBuildFilesPath + "/NoRedist", UE4BuildPath, Replacements);

			//Extract AAR and Jar files with dependencies
			ExtractAARAndJARFiles(EngineDirectory, UE4BuildPath, NDKArches);

			//Now validate GooglePlay app_id if enabled
			ValidateGooglePlay(UE4BuildPath);

			//determine which orientation requirements this app has
			bool bNeedLandscape = false;
			bool bNeedPortrait = false;
			DetermineScreenOrientationRequirements(out bNeedPortrait, out bNeedLandscape);

			//Now keep the splash screen images matching orientation requested
			PickSplashScreenOrientation(UE4BuildPath, bNeedPortrait, bNeedLandscape);

			//Similarly, keep only the downloader screen image matching the orientation requested
			PickDownloaderScreenOrientation(UE4BuildPath, bNeedPortrait, bNeedLandscape);

			// at this point, we can write out the cached build settings to compare for a next build
			File.WriteAllText(BuildSettingsCacheFile, CurrentBuildSettings);

			// at this point, we can write out the cached build settings to compare for a next build
			File.WriteAllText(BuildSettingsCacheFile, CurrentBuildSettings);

			///////////////
			// in case the game had an AndroidManifest.xml file, we overwrite it now with the generated one
			//File.WriteAllText(ManifestFile, NewManifest);
			///////////////

			Log.TraceInformation("\n===={0}====PREPARING NATIVE CODE=================================================================", DateTime.Now.ToString());
			bool HasNDKPath = File.Exists(NDKBuildPath);

			// get Ant verbosity level
			ConfigCacheIni Ini = GetConfigCacheIni("Engine");
			string AntVerbosity;
			Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "AntVerbosity", out AntVerbosity);

			foreach (var build in BuildList)
			{
				string Arch = build.Item1;
				string GPUArchitecture = build.Item2;
				string Manifest = build.Item3;
				string NDKArch = GetNDKArch(Arch);

				string SourceSOName = AndroidToolChain.InlineArchName(OutputPath, Arch, GPUArchitecture);
				// if the source binary was UE4Game, replace it with the new project name, when re-packaging a binary only build
				string ApkFilename = Path.GetFileNameWithoutExtension(OutputPath).Replace("UE4Game", ProjectName);
				string DestApkName = Path.Combine(ProjectDirectory, "Binaries/Android/") + ApkFilename + ".apk";

				// As we are always making seperate APKs we need to put the architecture into the name
				DestApkName = AndroidToolChain.InlineArchName(DestApkName, Arch, GPUArchitecture);

				// Write the manifest to the correct locations (cache and real)
				String ManifestFile = Path.Combine(IntermediateAndroidPath, Arch + "_" + GPUArchitecture + "_AndroidManifest.xml");
				File.WriteAllText(ManifestFile, Manifest);
				ManifestFile = Path.Combine(UE4BuildPath, "AndroidManifest.xml");
				File.WriteAllText(ManifestFile, Manifest);

				// copy prebuild plugin files
				UPL.ProcessPluginNode(NDKArch, "prebuildCopies", "");

				// update metadata files (like project.properties, build.xml) if we are missing a build.xml or if we just overwrote project.properties with a bad version in it (from game/engine dir)
				UpdateProjectProperties(ToolChain, UE4BuildPath, ProjectName);

				// update GameActivity.java if out of date
				UpdateGameActivity(Arch, NDKArch, EngineDirectory, UE4BuildPath);

				// 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);
				}

				String FinalSOName;

				if (HasNDKPath)
				{
					string LibDir = UE4BuildPath + "/jni/" + GetNDKArch(Arch);
					FinalSOName = LibDir + "/libUE4.so";

					// check to see if libUE4.so needs to be copied
					if (BuildListComboTotal > 1 || FilesAreDifferent(SourceSOName, FinalSOName))
					{
						Log.TraceInformation("\nCopying new .so {0} file to jni folder...", SourceSOName);
						Directory.CreateDirectory(LibDir);
						// copy the binary to the standard .so location
						File.Copy(SourceSOName, FinalSOName, true);
					}
				}
				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/" + GetNDKArch(Arch) + "/libUE4.so";

					// check to see if libUE4.so needs to be copied
					if (BuildListComboTotal > 1 || FilesAreDifferent(SourceSOName, FinalSOName))
					{
						Log.TraceInformation("\nCopying .so {0} file to jni folder...", SourceSOName);
						Directory.CreateDirectory(Path.GetDirectoryName(FinalSOName));
						File.Copy(SourceSOName, FinalSOName, true);
					}
				}

				// remove any read only flags
				FileInfo DestFileInfo = new FileInfo(FinalSOName);
				DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly;
				File.SetLastWriteTimeUtc(FinalSOName, File.GetLastWriteTimeUtc(SourceSOName));

				// if we need to run ndk-build, do it now
				if (HasNDKPath)
				{
					string LibSOName = UE4BuildPath + "/libs/" + GetNDKArch(Arch) + "/libUE4.so";
					// always delete libs up to this point so fat binaries and incremental builds work together (otherwise we might end up with multiple
					// so files in an apk that doesn't want them)
					// note that we don't want to delete all libs, just the ones we copied
					TimeSpan Diff = File.GetLastWriteTimeUtc(LibSOName) - File.GetLastWriteTimeUtc(FinalSOName);
					if (!File.Exists(LibSOName) || Diff.TotalSeconds < -1 || Diff.TotalSeconds > 1)
					{
						foreach (string Lib in Directory.EnumerateFiles(UE4BuildPath + "/libs", "libUE4*.so", SearchOption.AllDirectories))
						{
							File.Delete(Lib);
						}

						string CommandLine = "APP_ABI=\"" + GetNDKArch(Arch) + " " + "\"";
						if (!bForDistribution)
						{
							CommandLine += " NDK_DEBUG=1";
						}
						RunCommandLineProgramWithException(UE4BuildPath, NDKBuildPath, CommandLine, "Preparing native code for debugging...", true);

						File.SetLastWriteTimeUtc(LibSOName, File.GetLastWriteTimeUtc(FinalSOName));
					}
				}

				// after ndk-build is called, we can now copy in the stl .so (ndk-build deletes old files)
				// copy libgnustl_shared.so to library (use 4.8 if possible, otherwise 4.6)
				CopySTL(ToolChain, UE4BuildPath, Arch, NDKArch, bForDistribution);
				CopyGfxDebugger(UE4BuildPath, Arch, NDKArch);

				// copy postbuild plugin files
				UPL.ProcessPluginNode(NDKArch, "resourceCopies", "");

				Log.TraceInformation("\n===={0}====PERFORMING FINAL APK PACKAGE OPERATION================================================", DateTime.Now.ToString());

                string AntBuildType = "debug";
                string AntOutputSuffix = "-debug";
                if (bForDistribution)
                {
					// Generate the Proguard file contents and write it
					string ProguardContents = GenerateProguard(NDKArch, UE4BuildFilesPath, GameBuildFilesPath);
					string ProguardFilename = Path.Combine(UE4BuildPath, "proguard-project.txt");
					if (File.Exists(ProguardFilename))
					{
						File.Delete(ProguardFilename);
					}
					File.WriteAllText(ProguardFilename, ProguardContents);

					// this will write out ant.properties with info needed to sign a distribution build
					PrepareToSignApk(UE4BuildPath);
					AntBuildType = "release";
					AntOutputSuffix = "-release";
				}

				// Use ant to build the .apk file
				string ShellExecutable = Utils.IsRunningOnMono ? "/bin/sh" : "cmd.exe";
				string ShellParametersBegin = Utils.IsRunningOnMono ? "-c '" : "/c ";
				string ShellParametersEnd = Utils.IsRunningOnMono ? "'" : "";
				switch (AntVerbosity.ToLower())
				{
					default:
					case "quiet":
						if (RunCommandLineProgramAndReturnResult(UE4BuildPath, ShellExecutable, ShellParametersBegin + "\"" + GetAntPath() + "\" -quiet " + AntBuildType + ShellParametersEnd, "Making .apk with Ant... (note: it's safe to ignore javac obsolete warnings)") != 0)
						{
							RunCommandLineProgramAndReturnResult(UE4BuildPath, ShellExecutable, ShellParametersBegin + "\"" + GetAntPath() + "\" " + AntBuildType + ShellParametersEnd, "Making .apk with Ant again to show errors");
						}
						break;

					case "normal":
						RunCommandLineProgramAndReturnResult(UE4BuildPath, ShellExecutable, ShellParametersBegin + "\"" + GetAntPath() + "\" " + AntBuildType + ShellParametersEnd, "Making .apk with Ant again to show errors");
						break;

					case "verbose":
						RunCommandLineProgramAndReturnResult(UE4BuildPath, ShellExecutable, ShellParametersBegin + "\"" + GetAntPath() + "\" -verbose " + AntBuildType + ShellParametersEnd, "Making .apk with Ant again to show errors");
						break;
				}

				// make sure destination exists
				Directory.CreateDirectory(Path.GetDirectoryName(DestApkName));

				// now copy to the final location
				File.Copy(UE4BuildPath + "/bin/" + ProjectName + AntOutputSuffix + ".apk", DestApkName, true);

			}

		}