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