private string GetBestGPUArchitecture(ProjectParams Params)
    {
        bool bMakeSeparateApks = UnrealBuildTool.Android.UEDeployAndroid.ShouldMakeSeparateApks();

        // if we are joining all .so's into a single .apk, there's no need to find the best one - there is no other one
        if (!bMakeSeparateApks)
        {
            return("");
        }

        string[] AppGPUArchitectures = AndroidToolChain.GetAllGPUArchitectures();

        // get the device extensions
        ProcessResult ExtensionsResult = RunAdbCommand(Params, "shell dumpsys SurfaceFlinger", null, ERunOptions.AppMustExist);
        string        Extensions       = ExtensionsResult.Output.Trim();

        // look for AEP support
        if (Extensions.Contains("GL_ANDROID_extension_pack_es31a") && Extensions.Contains("GL_EXT_color_buffer_half_float"))
        {
            if (AppGPUArchitectures.Contains("-es31"))
            {
                return("-es31");
            }
        }

        return("-es2");
    }
Exemplo n.º 2
0
        private string GetAllBuildSettings(string BuildPath, bool bForDistribution, bool bMakeSeparateApks, bool bOBBinApk)
        {
            // make the settings string - this will be char by char compared against last time
            StringBuilder CurrentSettings = new StringBuilder();

            CurrentSettings.AppendFormat("NDKROOT={0}{1}", Environment.GetEnvironmentVariable("NDKROOT"), Environment.NewLine);
            CurrentSettings.AppendFormat("ANDROID_HOME={0}{1}", Environment.GetEnvironmentVariable("ANDROID_HOME"), Environment.NewLine);
            CurrentSettings.AppendFormat("ANT_HOME={0}{1}", Environment.GetEnvironmentVariable("ANT_HOME"), Environment.NewLine);
            CurrentSettings.AppendFormat("JAVA_HOME={0}{1}", Environment.GetEnvironmentVariable("JAVA_HOME"), Environment.NewLine);
            CurrentSettings.AppendFormat("SDKVersion={0}{1}", GetSdkApiLevel(), Environment.NewLine);
            CurrentSettings.AppendFormat("bForDistribution={0}{1}", bForDistribution, Environment.NewLine);
            CurrentSettings.AppendFormat("bMakeSeparateApks={0}{1}", bMakeSeparateApks, Environment.NewLine);
            CurrentSettings.AppendFormat("bOBBinApk={0}{1}", bOBBinApk, Environment.NewLine);

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

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

            return(CurrentSettings.ToString());
        }
Exemplo n.º 3
0
        public override bool PrepTargetForDeployment(UEBuildTarget InTarget)
        {
            // we need to strip architecture from any of the output paths
            string BaseSoName = AndroidToolChain.RemoveArchName(InTarget.OutputPaths[0]);

            // this always makes a merged .apk since for debugging, there's no way to know which one to run
            MakeApk(InTarget.AppName, InTarget.ProjectDirectory, BaseSoName, BuildConfiguration.RelativeEnginePath, bForDistribution: false, CookFlavor: "", bMakeSeparateApks: false);
            return(true);
        }
Exemplo n.º 4
0
        private static string GetSdkApiLevel()
        {
            if (CachedSDKLevel == null)
            {
                // default to looking on disk for latest API level
                string Target = AndroidPlatform.AndroidSdkApiTarget;

                // if we want to use whatever version the ndk uses, then use that
                if (Target == "matchndk")
                {
                    Target = AndroidToolChain.GetNdkApiLevel();
                }

                // run a command and capture output
                if (Target == "latest")
                {
                    // we expect there to be one, so use the first one
                    string AndroidCommandPath = Environment.ExpandEnvironmentVariables("%ANDROID_HOME%/tools/android.bat");

                    var ExeInfo = new ProcessStartInfo(AndroidCommandPath, "list targets");
                    ExeInfo.UseShellExecute        = false;
                    ExeInfo.RedirectStandardOutput = true;
                    using (var GameProcess = Process.Start(ExeInfo))
                    {
                        PossibleApiLevels = new List <string>();
                        GameProcess.BeginOutputReadLine();
                        GameProcess.OutputDataReceived += ParseApiLevel;
                        GameProcess.WaitForExit();
                    }

                    if (PossibleApiLevels != null && PossibleApiLevels.Count > 0)
                    {
                        Target = AndroidToolChain.GetLargestApiLevel(PossibleApiLevels.ToArray());
                    }
                    else
                    {
                        throw new BuildException("Can't make an APK an API installed (see \"android.bat list targets\")");
                    }
                }

                Console.WriteLine("Building Java with SDK API '{0}'", Target);
                CachedSDKLevel = Target;
            }

            return(CachedSDKLevel);
        }
    private string GetBestDeviceArchitecture(ProjectParams Params)
    {
        bool bMakeSeparateApks = UnrealBuildTool.Android.UEDeployAndroid.ShouldMakeSeparateApks();

        // if we are joining all .so's into a single .apk, there's no need to find the best one - there is no other one
        if (!bMakeSeparateApks)
        {
            return("");
        }

        string[] AppArchitectures = AndroidToolChain.GetAllArchitectures();

        // ask the device
        ProcessResult ABIResult = RunAdbCommand(Params, " shell getprop ro.product.cpu.abi", null, ERunOptions.AppMustExist);

        // the output is just the architecture
        string DeviceArch = UnrealBuildTool.Android.UEDeployAndroid.GetUE4Arch(ABIResult.Output.Trim());

        // if the architecture wasn't built, look for a backup
        if (Array.IndexOf(AppArchitectures, DeviceArch) == -1)
        {
            // go from 64 to 32-bit
            if (DeviceArch == "-arm64")
            {
                DeviceArch = "-armv7";
            }
            // go from 64 to 32-bit
            else if (DeviceArch == "-x86_64")
            {
                if (Array.IndexOf(AppArchitectures, "-x86") == -1)
                {
                    DeviceArch = "-x86";
                }
                // if it didn't have 32-bit x86, look for 64-bit arm for emulation
                // @todo android 64-bit: x86_64 most likely can't emulate arm64 at this ponit
//              else if (Array.IndexOf(AppArchitectures, "-arm64") == -1)
//              {
//                  DeviceArch = "-arm64";
//              }
                // finally try for 32-bit arm emulation (Houdini)
                else
                {
                    DeviceArch = "-armv7";
                }
            }
            // use armv7 (with Houdini emulation)
            else if (DeviceArch == "-x86")
            {
                DeviceArch = "-armv7";
            }
        }

        // if after the fallbacks, we still don't have it, we can't continue
        if (Array.IndexOf(AppArchitectures, DeviceArch) == -1)
        {
            string ErrorString = String.Format("Unable to run because you don't have an apk that is usable on {0}", Params.Device);
            ErrorReporter.Error(ErrorString, (int)ErrorCodes.Error_NoApkSuitableForArchitecture);
            throw new AutomationException(ErrorString);
        }

        return(DeviceArch);
    }
Exemplo n.º 6
0
        private void MakeApk(string ProjectName, string ProjectDirectory, string OutputPath, string EngineDirectory, bool bForDistribution, string CookFlavor, bool bMakeSeparateApks)
        {
            // cache some tools paths
            string AndroidCommandPath = Environment.ExpandEnvironmentVariables("%ANDROID_HOME%/tools/android.bat");
            string NDKBuildPath       = Environment.ExpandEnvironmentVariables("%NDKROOT%/ndk-build.cmd");
            string AntBuildPath       = GetAntPath();

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

            string[] Arches           = AndroidToolChain.GetAllArchitectures();
            string[] GPUArchitectures = AndroidToolChain.GetAllGPUArchitectures();
            int      NumArches        = Arches.Length * GPUArchitectures.Length;

            // See if we need to create a 'default' Java Build settings file if one doesn't exist (if it does exist we have to assume it has been setup correctly)
            string UE4JavaBuildSettingsFileName = GetUE4JavaBuildSettingsFileName(EngineDirectory);

            WriteJavaBuildSettingsFile(UE4JavaBuildSettingsFileName, UEBuildConfiguration.bOBBinAPK);

            // first check if all .so's are up to date
            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));
                    }

                    // rebuild if .ini files change
                    // @todo android: programmatically determine if any .ini setting changed?
                    InputFiles.Add(Path.Combine(EngineDirectory, "Config\\BaseEngine.ini"));
                    InputFiles.Add(Path.Combine(ProjectDirectory, "Config\\DefaultEngine.ini"));

                    // make sure changed java settings will rebuild apk
                    InputFiles.Add(UE4JavaBuildSettingsFileName);

                    // rebuild if .pak files exist for OBB in APK case
                    if (UEBuildConfiguration.bOBBinAPK)
                    {
                        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;
                            }
                        }
                    }
                }
            }
            if (bAllInputsCurrent)
            {
                Log.TraceInformation("Output .apk file(s) are up to date (compared to the .so and .java input files)");
                return;
            }

            // Once for all arches code:

            // make up a dictionary of strings to replace in the Manifest file
            Dictionary <string, string> Replacements = new Dictionary <string, string>();

            Replacements.Add("${EXECUTABLE_NAME}", ProjectName);

            // distribution apps can't be debuggable, so if it was set to true, set it to false:
            if (bForDistribution)
            {
                Replacements.Add("android:debuggable=\"true\"", "android:debuggable=\"false\"");
            }

            // Update the Google Play services lib with the target platform version currently in use.
            // This appears to be required for the build to work without errors when Play services are referenced by the game.
            // This will try to modify existing files, like project.properties, so we copy the entire library into
            // an intermediate directory and work from there.
            string GooglePlayServicesSourcePath       = Path.GetFullPath(Path.Combine(EngineDirectory, "Build/Android/Java/google-play-services_lib_rev19/"));
            string GooglePlayServicesIntermediatePath = Path.GetFullPath(Path.Combine(IntermediateAndroidPath, "google-play-services_lib/"));

            DeleteDirectory(GooglePlayServicesIntermediatePath);
            CopyFileDirectory(GooglePlayServicesSourcePath, GooglePlayServicesIntermediatePath, new Dictionary <string, string>());

            ProcessStartInfo AndroidBatStartInfoPlayServicesLib = new ProcessStartInfo();

            AndroidBatStartInfoPlayServicesLib.WorkingDirectory = GooglePlayServicesIntermediatePath;
            AndroidBatStartInfoPlayServicesLib.FileName         = AndroidCommandPath;
            AndroidBatStartInfoPlayServicesLib.Arguments        = "update project " + " --path . --target " + GetSdkApiLevel();
            AndroidBatStartInfoPlayServicesLib.UseShellExecute  = false;
            Console.WriteLine("\nRunning: " + AndroidBatStartInfoPlayServicesLib.FileName + " " + AndroidBatStartInfoPlayServicesLib.Arguments);
            Process AndroidBatPlayServicesLib = new Process();

            AndroidBatPlayServicesLib.StartInfo = AndroidBatStartInfoPlayServicesLib;
            AndroidBatPlayServicesLib.Start();
            AndroidBatPlayServicesLib.WaitForExit();

            // android bat failure
            if (AndroidBatPlayServicesLib.ExitCode != 0)
            {
                throw new BuildException("android.bat failed [{0}]", AndroidBatStartInfoPlayServicesLib.Arguments);
            }

            //need to create separate run for each lib. Will be added to project.properties in order in which they are added
            //the order is important.
            //as android.library.reference.X=libpath where X = 1 - N
            //for e.g this one will be added as android.library.reference.1=<EngineDirectory>/Source/ThirdParty/Android/google_play_services_lib

            // Ant seems to need a relative path to work
            Uri    ServicesBuildUri    = new Uri(GooglePlayServicesIntermediatePath);
            Uri    ProjectUri          = new Uri(UE4BuildPath + "/");
            string RelativeServicesUri = ProjectUri.MakeRelativeUri(ServicesBuildUri).ToString();

            string FinalNdkBuildABICommand = "";

            // now make the apk(s)
            for (int ArchIndex = 0; ArchIndex < Arches.Length; ArchIndex++)
            {
                string Arch = Arches[ArchIndex];
                for (int GPUArchIndex = 0; GPUArchIndex < GPUArchitectures.Length; GPUArchIndex++)
                {
                    string GPUArchitecture = GPUArchitectures[GPUArchIndex];
                    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";

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

                    // code in here will run once per .apk (or once if merged apk)
                    if (bMakeSeparateApks || ArchIndex == 0)
                    {
                        //Wipe the Intermediate/Build/APK directory first
                        DeleteDirectory(UE4BuildPath);

                        // If we are packaging for Amazon then we need to copy the PAK files to the correct location
                        // Currently we'll just support 1 of 'em
                        if (UEBuildConfiguration.bOBBinAPK)
                        {
                            string PAKFileLocation = ProjectDirectory + "/Saved/StagedBuilds/Android" + CookFlavor + "/" + ProjectName + "/Content/Paks";
                            Console.WriteLine("Pak location {0}", PAKFileLocation);
                            string PAKFileDestination = UE4BuildPath + "/assets";
                            Console.WriteLine("Pak destination location {0}", PAKFileDestination);
                            if (Directory.Exists(PAKFileLocation))
                            {
                                Directory.CreateDirectory(UE4BuildPath);
                                Directory.CreateDirectory(PAKFileDestination);
                                Console.WriteLine("PAK file exists...");
                                var PakFiles = Directory.EnumerateFiles(PAKFileLocation, "*.pak", SearchOption.TopDirectoryOnly);
                                foreach (var Name in PakFiles)
                                {
                                    Console.WriteLine("Found file {0}", Name);
                                }

                                if (PakFiles.Count() > 0)
                                {
                                    var DestFileName = Path.Combine(PAKFileDestination, Path.GetFileName(PakFiles.ElementAt(0)) + ".png");                             // Need a rename to turn off compression
                                    var SrcFileName  = PakFiles.ElementAt(0);
                                    if (!File.Exists(DestFileName) || File.GetLastWriteTimeUtc(DestFileName) < File.GetLastWriteTimeUtc(SrcFileName))
                                    {
                                        Console.WriteLine("Copying {0} to {1}", SrcFileName, DestFileName);
                                        File.Copy(SrcFileName, DestFileName);
                                    }
                                }
                            }
                            // Do we want to kill the OBB here or not???
                        }

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

                        //Android.bat for game-specific
                        ProcessStartInfo AndroidBatStartInfoGame = new ProcessStartInfo();
                        AndroidBatStartInfoGame.WorkingDirectory = UE4BuildPath;
                        AndroidBatStartInfoGame.FileName         = AndroidCommandPath;
                        AndroidBatStartInfoGame.Arguments        = "update project --name " + ProjectName + " --path . --target " + GetSdkApiLevel();
                        AndroidBatStartInfoGame.UseShellExecute  = false;
                        Console.WriteLine("\nRunning: " + AndroidBatStartInfoGame.FileName + " " + AndroidBatStartInfoGame.Arguments);
                        Process AndroidBatGame = new Process();
                        AndroidBatGame.StartInfo = AndroidBatStartInfoGame;
                        AndroidBatGame.Start();
                        AndroidBatGame.WaitForExit();

                        // android bat failure
                        if (AndroidBatGame.ExitCode != 0)
                        {
                            throw new BuildException("android.bat failed [{0}]", AndroidBatStartInfoGame.Arguments);
                        }

                        AndroidBatStartInfoGame.Arguments = " update project --name " + ProjectName + " --path .  --target " + GetSdkApiLevel() + " --library " + RelativeServicesUri;
                        Console.WriteLine("\nRunning: " + AndroidBatStartInfoGame.FileName + " " + AndroidBatStartInfoGame.Arguments);
                        AndroidBatGame.Start();
                        AndroidBatGame.WaitForExit();

                        if (AndroidBatGame.ExitCode != 0)
                        {
                            throw new BuildException("android.bat failed [{0}]", AndroidBatStartInfoGame.Arguments);
                        }
                    }

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

                    // Use ndk-build to do stuff and move the .so file to the lib folder (only if NDK is installed)
                    string FinalSOName = "";
                    if (File.Exists(NDKBuildPath))
                    {
                        string LibDir = UE4BuildPath + "/jni/" + GetNDKArch(Arch);
                        Directory.CreateDirectory(LibDir);

                        // copy the binary to the standard .so location
                        FinalSOName = LibDir + "/libUE4.so";
                        File.Copy(SourceSOName, FinalSOName, true);

                        FinalNdkBuildABICommand += GetNDKArch(Arch) + " ";
                    }
                    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";
                        Directory.CreateDirectory(Path.GetDirectoryName(FinalSOName));
                        File.Copy(SourceSOName, FinalSOName);
                    }

                    // now do final stuff per apk (or after all .so's for a shared .apk)
                    if (bMakeSeparateApks || ArchIndex == NumArches - 1)
                    {
                        // if we need to run ndk-build, do it now (if making a shared .apk, we need to wait until all .libs exist)
                        if (!string.IsNullOrEmpty(FinalNdkBuildABICommand))
                        {
                            ProcessStartInfo NDKBuildInfo = new ProcessStartInfo();
                            NDKBuildInfo.WorkingDirectory = UE4BuildPath;
                            NDKBuildInfo.FileName         = NDKBuildPath;
                            NDKBuildInfo.Arguments        = "APP_ABI=\"" + FinalNdkBuildABICommand + "\"";
                            FinalNdkBuildABICommand       = "";
                            if (!bForDistribution)
                            {
                                NDKBuildInfo.Arguments += " NDK_DEBUG=1";
                            }
                            NDKBuildInfo.UseShellExecute = true;
                            NDKBuildInfo.WindowStyle     = ProcessWindowStyle.Minimized;
                            Console.WriteLine("\nRunning: " + NDKBuildInfo.FileName + " " + NDKBuildInfo.Arguments);
                            Process NDKBuild = new Process();
                            NDKBuild.StartInfo = NDKBuildInfo;
                            NDKBuild.Start();
                            NDKBuild.WaitForExit();

                            // ndk build failure
                            if (NDKBuild.ExitCode != 0)
                            {
                                throw new BuildException("ndk-build failed [{0}]", NDKBuildInfo.Arguments);
                            }
                        }

                        // 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)
                        if (bMakeSeparateApks)
                        {
                            CopySTL(UE4BuildPath, Arch);
                            CopyGfxDebugger(UE4BuildPath, Arch);
                        }
                        else
                        {
                            foreach (string InnerArch in Arches)
                            {
                                CopySTL(UE4BuildPath, InnerArch);
                                CopyGfxDebugger(UE4BuildPath, InnerArch);
                            }
                        }


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

                        // Use ant debug to build the .apk file
                        ProcessStartInfo CallAntStartInfo = new ProcessStartInfo();
                        CallAntStartInfo.WorkingDirectory = UE4BuildPath;
                        CallAntStartInfo.FileName         = "cmd.exe";
                        CallAntStartInfo.Arguments        = "/c \"" + AntBuildPath + "\"  " + (bForDistribution ? "release" : "debug");
                        CallAntStartInfo.UseShellExecute  = false;
                        Console.WriteLine("\nRunning: " + CallAntStartInfo.Arguments);
                        Process CallAnt = new Process();
                        CallAnt.StartInfo = CallAntStartInfo;
                        CallAnt.Start();
                        CallAnt.WaitForExit();

                        // ant failure
                        if (CallAnt.ExitCode != 0)
                        {
                            throw new BuildException("ant.bat failed [{0}]", CallAntStartInfo.Arguments);
                        }

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

                        // do we need to sign for distro?
                        if (bForDistribution)
                        {
                            // use diffeent source and dest apk's for signed mode
                            string SourceApkName = UE4BuildPath + "/bin/" + ProjectName + "-release-unsigned.apk";
                            SignApk(UE4BuildPath + "/SigningConfig.xml", SourceApkName, DestApkName);
                        }
                        else
                        {
                            // now copy to the final location
                            File.Copy(UE4BuildPath + "/bin/" + ProjectName + "-debug" + ".apk", DestApkName, true);
                        }
                    }
                }
            }
        }
Exemplo n.º 7
0
        private void MakeApk(string ProjectName, string ProjectDirectory, string OutputPath, string EngineDirectory, bool bForDistribution, string CookFlavor, bool bMakeSeparateApks, bool bIncrementalPackage)
        {
            Log.TraceInformation("\n===={0}====PREPARING TO MAKE APK=================================================================", DateTime.Now.ToString());

            // cache some tools paths
            string AndroidCommandPath = Environment.ExpandEnvironmentVariables("%ANDROID_HOME%/tools/android.bat");
            string NDKBuildPath       = Environment.ExpandEnvironmentVariables("%NDKROOT%/ndk-build.cmd");

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

            string[] Arches           = AndroidToolChain.GetAllArchitectures();
            string[] GPUArchitectures = AndroidToolChain.GetAllGPUArchitectures();
            int      NumArches        = Arches.Length * GPUArchitectures.Length;


            // See if we need to create a 'default' Java Build settings file if one doesn't exist (if it does exist we have to assume it has been setup correctly)
            string UE4JavaBuildSettingsFileName = GetUE4JavaBuildSettingsFileName(EngineDirectory);

            WriteJavaBuildSettingsFile(UE4JavaBuildSettingsFileName, UEBuildConfiguration.bOBBinAPK);

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

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

            if (File.Exists(BuildSettingsCacheFile))
            {
                string PreviousBuildSettings = File.ReadAllText(BuildSettingsCacheFile);
                if (PreviousBuildSettings == CurrentBuildSettings)
                {
                    bBuildSettingsMatch = true;
                }
                else
                {
                    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
                bool bAllInputsCurrent = CheckDependencies(ProjectName, ProjectDirectory, UE4BuildFilesPath, GameBuildFilesPath,
                                                           EngineDirectory, UE4JavaBuildSettingsFileName, CookFlavor, OutputPath, UE4BuildPath, bMakeSeparateApks);

                if (bAllInputsCurrent)
                {
                    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 the Manifest file
            Dictionary <string, string> Replacements = new Dictionary <string, string>();

            Replacements.Add("${EXECUTABLE_NAME}", ProjectName);

            // distribution apps can't be debuggable, so if it was set to true, set it to false:
            if (bForDistribution)
            {
                Replacements.Add("android:debuggable=\"true\"", "android:debuggable=\"false\"");
            }

            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 PAK files to the correct location
            // Currently we'll just support 1 of 'em
            if (UEBuildConfiguration.bOBBinAPK)
            {
                string PAKFileLocation = ProjectDirectory + "/Saved/StagedBuilds/Android" + CookFlavor + "/" + ProjectName + "/Content/Paks";
                Console.WriteLine("Pak location {0}", PAKFileLocation);
                string PAKFileDestination = UE4BuildPath + "/assets";
                Console.WriteLine("Pak destination location {0}", PAKFileDestination);
                if (Directory.Exists(PAKFileLocation))
                {
                    Directory.CreateDirectory(UE4BuildPath);
                    Directory.CreateDirectory(PAKFileDestination);
                    Console.WriteLine("PAK file exists...");
                    var PakFiles = Directory.EnumerateFiles(PAKFileLocation, "*.pak", SearchOption.TopDirectoryOnly);
                    foreach (var Name in PakFiles)
                    {
                        Console.WriteLine("Found file {0}", Name);
                    }

                    if (PakFiles.Count() > 0)
                    {
                        var DestFileName = Path.Combine(PAKFileDestination, Path.GetFileName(PakFiles.ElementAt(0)) + ".png");                         // Need a rename to turn off compression
                        var SrcFileName  = PakFiles.ElementAt(0);
                        if (!File.Exists(DestFileName) || File.GetLastWriteTimeUtc(DestFileName) < File.GetLastWriteTimeUtc(SrcFileName))
                        {
                            Console.WriteLine("Copying {0} to {1}", SrcFileName, DestFileName);
                            File.Copy(SrcFileName, DestFileName);
                        }
                    }
                }
                // Do we want to kill the OBB here or not???
            }

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

            // 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(UE4BuildPath, ProjectName);

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

            // now make the apk(s)
            string FinalNdkBuildABICommand = "";

            for (int ArchIndex = 0; ArchIndex < Arches.Length; ArchIndex++)
            {
                string Arch = Arches[ArchIndex];
                for (int GPUArchIndex = 0; GPUArchIndex < GPUArchitectures.Length; GPUArchIndex++)
                {
                    Log.TraceInformation("\n===={0}====PREPARING NATIVE CODE=================================================================", DateTime.Now.ToString());

                    string GPUArchitecture = GPUArchitectures[GPUArchIndex];
                    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";

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

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

                    // Use ndk-build to do stuff and move the .so file to the lib folder (only if NDK is installed)
                    string FinalSOName = "";
                    if (File.Exists(NDKBuildPath))
                    {
                        string LibDir = UE4BuildPath + "/jni/" + GetNDKArch(Arch);
                        Directory.CreateDirectory(LibDir);

                        // copy the binary to the standard .so location
                        FinalSOName = LibDir + "/libUE4.so";
                        File.Copy(SourceSOName, FinalSOName, true);

                        FinalNdkBuildABICommand += GetNDKArch(Arch) + " ";
                    }
                    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";
                        Directory.CreateDirectory(Path.GetDirectoryName(FinalSOName));
                        File.Copy(SourceSOName, FinalSOName);
                    }

                    // now do final stuff per apk (or after all .so's for a shared .apk)
                    if (bMakeSeparateApks || ArchIndex == NumArches - 1)
                    {
                        // 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 (
                        foreach (string Lib in Directory.EnumerateFiles(UE4BuildPath + "/libs", "libUE4*.so", SearchOption.AllDirectories))
                        {
                            File.Delete(Lib);
                        }

                        // if we need to run ndk-build, do it now (if making a shared .apk, we need to wait until all .libs exist)
                        if (!string.IsNullOrEmpty(FinalNdkBuildABICommand))
                        {
                            string CommandLine = "APP_ABI=\"" + FinalNdkBuildABICommand + "\"";
                            if (!bForDistribution)
                            {
                                CommandLine += " NDK_DEBUG=1";
                            }
                            RunCommandLineProgramAndThrowOnError(UE4BuildPath, NDKBuildPath, CommandLine, "Preparing native code for debugging...", true);

                            // next loop we don't want to redo these ABIs
                            FinalNdkBuildABICommand = "";
                        }

                        // 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)
                        if (bMakeSeparateApks)
                        {
                            CopySTL(UE4BuildPath, Arch);
                            CopyGfxDebugger(UE4BuildPath, Arch);
                        }
                        else
                        {
                            foreach (string InnerArch in Arches)
                            {
                                CopySTL(UE4BuildPath, InnerArch);
                                CopyGfxDebugger(UE4BuildPath, InnerArch);
                            }
                        }


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

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

                        string AntBuildType    = "debug";
                        string AntOutputSuffix = "-debug";
                        if (bForDistribution)
                        {
                            // 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
                        RunCommandLineProgramAndThrowOnError(UE4BuildPath, "cmd.exe", "/c \"" + GetAntPath() + "\" -quiet " + AntBuildType, "Making .apk with Ant... (note: it's safe to ignore javac obsolete warnings)");

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

                        // now copy to the final location
                        File.Copy(UE4BuildPath + "/bin/" + ProjectName + AntOutputSuffix + ".apk", DestApkName, true);
                    }
                }
            }
        }
Exemplo n.º 8
0
        private bool CheckDependencies(string ProjectName, string ProjectDirectory, string UE4BuildFilesPath, string GameBuildFilesPath, string EngineDirectory, string JavaSettingsFile, string CookFlavor, string OutputPath, string UE4BuildPath, bool bMakeSeparateApks)
        {
            string[] Arches           = AndroidToolChain.GetAllArchitectures();
            string[] GPUArchitectures = AndroidToolChain.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));
                    }

                    // rebuild if .ini files change
                    // @todo android: programmatically determine if any .ini setting changed?
                    InputFiles.Add(Path.Combine(EngineDirectory, "Config\\BaseEngine.ini"));
                    InputFiles.Add(Path.Combine(ProjectDirectory, "Config\\DefaultEngine.ini"));

                    // make sure changed java settings will rebuild apk
                    InputFiles.Add(JavaSettingsFile);

                    // rebuild if .pak files exist for OBB in APK case
                    if (UEBuildConfiguration.bOBBinAPK)
                    {
                        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);
        }