using System; using System.Collections; using System.Reflection; using UnityEditor; using UnityEditor.Build.Reporting; using Ubisoft.Hotel.Addressables.Core.Editor; namespace Ubisoft.Hotel.PackageManager.Editor { internal class BuildStep_BuildPlayer : BuildStep { internal const string StepName = "BuildStep_BuildPlayer"; internal static BuildPlayerOptions GetBuildPlayerOptionsViaReflection(bool askForLocation = false, BuildPlayerOptions defaultOptions = new BuildPlayerOptions()) { // Solution gotten from here: https://answers.unity.com/questions/1642506/getting-the-current-buildoptions.html // Get static internal "GetBuildPlayerOptionsInternal" method MethodInfo method = typeof(BuildPlayerWindow.DefaultBuildMethods).GetMethod("GetBuildPlayerOptionsInternal", BindingFlags.NonPublic | BindingFlags.Static); // invoke internal method return (BuildPlayerOptions)method.Invoke( null, new object[] { askForLocation, defaultOptions }); } internal BuildStep_BuildPlayer() : base(StepName) { } private BuildPlayerOptions OnBuildPlayerOptions(BuildPlayerOptions options) { return options; } protected override void ExtendedPerform() { BuildPlayerWindow.RegisterGetBuildPlayerOptionsHandler(OnBuildPlayerOptions); bool askForLocation = false; EditorUserBuildSettings.installInBuildFolder = false; // Set location if it's null to avoid a crash the very first time this step is performed BuildTarget buildTarget = EditorUserBuildSettings.activeBuildTarget; string location = EditorUserBuildSettings.GetBuildLocation(buildTarget); if (string.IsNullOrEmpty(location)) { EditorUserBuildSettings.SetBuildLocation(buildTarget, BuildSettings.location); location = EditorUserBuildSettings.GetBuildLocation(buildTarget); } if (string.IsNullOrEmpty(location)) { PackageManager.LogWarning("BuildStep_BuildPlayer: Location is empty before building player!"); } switch (buildTarget) { case BuildTarget.Android: PrepareAndroidBuild(); break; case BuildTarget.iOS: PrepareiOSBuild(); break; } BuildPlayerOptions buildPlayerOptions = GetBuildPlayerOptions(buildTarget, askForLocation, new BuildPlayerOptions()); BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions); string time = report.summary.totalTime.ToString(@"hh\:mm\:ss"); PackageManager.Log($"{Name} ended in {time} with result {report.summary.result}"); // An exception is thrown when the result is not success to make sure the batch is done with the process if (report.summary.result != BuildResult.Succeeded) { throw new Exception($"BuildPlayerError"); } } private void PrepareAndroidBuild() { // Check if we need to fetch keystorePass and keyaliasPass from environment variables to sign for Android // only if no keys are set yet if (PlayerSettings.Android.useCustomKeystore && string.IsNullOrEmpty(PlayerSettings.Android.keystorePass)) { string keystorePass = Environment.GetEnvironmentVariable("UBM_UNITY_KEYSTORE_PASS"); if (!string.IsNullOrEmpty(keystorePass)) { PlayerSettings.Android.keystorePass = keystorePass; } string keyaliasPass = Environment.GetEnvironmentVariable("UBM_UNITY_KEYALIAS_PASS"); if (!string.IsNullOrEmpty(keyaliasPass)) { PlayerSettings.Android.keyaliasPass = keyaliasPass; } PackageManager.Log("BuildStep_BuildPlayer keystorePass: " + keystorePass + " keystoreAliasPass: " + keyaliasPass); } } private void PrepareiOSBuild() { #if UNITY_IOS && HT_ADDRESSABLES_DAD_ON PlayerSettings.iOS.useOnDemandResources = true; //BuildProcessorHelper.MoveDataForAppBundleBuild(); UnityEditor.iOS.BuildPipeline.collectResources += BuildProcessorHelper.GetIosAssetPackResources; #endif } private bool IsInstallInBuildFolderOption() { MethodInfo method = typeof(BuildPlayerWindow.DefaultBuildMethods).GetMethod("IsInstallInBuildFolderOption", BindingFlags.NonPublic | BindingFlags.Static); return (bool)method.Invoke(null, null); } private BuildPlayerOptions GetBuildPlayerOptions(BuildTarget buildTarget, bool askForLocation = false, BuildPlayerOptions defaultOptions = new BuildPlayerOptions()) { BuildPlayerOptions returnValue; // GetBuildPlayerOptionsViaReflection() is the way to get all BuildOptions set in Unity settings but // it causes nasty side effects: // -) It always prompts a popup to enter location on iOS // -) It might crash if no location has been set before // -) EditorUserBuildSettings.SetBuildLocation() does not set location on some platforms // For all these reasons this implementation is commented out until Unity fixes these issues. // As a consequence compression setting won't be applied when a non default value is set //if (buildTarget == BuildTarget.iOS && !askForLocation) //if (true) { returnValue = GetBuildPlayerOptionsInternal(defaultOptions); } /*else { returnValue = GetBuildPlayerOptionsViaReflection(askForLocation, defaultOptions); }*/ if (!askForLocation) { returnValue.locationPathName = BuildSettings.location; } return returnValue; } // This code has been taken and adapted from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/BuildPlayerWindowBuildMethods.cs // Compression settings are not taken into consideration because it's hard to adapt and it's not needed since // this method will be called only for iOS builds. private BuildPlayerOptions GetBuildPlayerOptionsInternal(BuildPlayerOptions defaultBuildPlayerOptions) { var options = defaultBuildPlayerOptions; bool updateExistingBuild = false; BuildTarget buildTarget = EditorUserBuildSettings.activeBuildTarget; BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup; bool developmentBuild = EditorUserBuildSettings.development; if (developmentBuild) { options.options |= BuildOptions.Development; } if (EditorUserBuildSettings.allowDebugging && developmentBuild) { options.options |= BuildOptions.AllowDebugging; } #if UNITY_2021_1_OR_NEWER if (EditorUserBuildSettings.symlinkSources) { options.options |= BuildOptions.SymlinkSources; } #else if (EditorUserBuildSettings.symlinkLibraries) { options.options |= BuildOptions.SymlinkLibraries; } if (EditorUserBuildSettings.enableHeadlessMode) { options.options |= BuildOptions.EnableHeadlessMode; } #endif if (EditorUserBuildSettings.connectProfiler && (developmentBuild || buildTarget == BuildTarget.WSAPlayer)) { options.options |= BuildOptions.ConnectWithProfiler; } #if UNITY_2019_1_OR_NEWER if (EditorUserBuildSettings.buildWithDeepProfilingSupport && developmentBuild) { options.options |= BuildOptions.EnableDeepProfilingSupport; } #endif if (EditorUserBuildSettings.buildScriptsOnly) { options.options |= BuildOptions.BuildScriptsOnly; } if (IsInstallInBuildFolderOption()) { options.options |= BuildOptions.InstallInBuildFolder; } if (updateExistingBuild) { options.options |= BuildOptions.AcceptExternalModificationsToPlayer; } options.target = buildTarget; options.targetGroup = buildTargetGroup; options.locationPathName = EditorUserBuildSettings.GetBuildLocation(buildTarget); options.assetBundleManifestPath = null; // Build a list of scenes that are enabled ArrayList scenesList = new ArrayList(); EditorBuildSettingsScene[] editorScenes = EditorBuildSettings.scenes; foreach (EditorBuildSettingsScene scene in editorScenes) { if (scene.enabled) { _ = scenesList.Add(scene.path); } } options.scenes = scenesList.ToArray(typeof(string)) as string[]; return options; } } }