using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEditor;
using UnityEditor.Build.Reporting;
///
/// Utility menus to easily create our builds for our playtests. If you're just exploring this project, you shouldn't need those. They are mostly to make
/// multiplatform build creation easier and is meant for internal usage.
///
internal static class BuildHelpers
{
const string k_MenuRoot = "Boss Room/Playtest Builds/";
const string k_Build = k_MenuRoot + "Build";
const string k_DeleteBuilds = k_MenuRoot + "Delete All Builds (keeps cache)";
const string k_AllToggleName = k_MenuRoot + "Toggle All";
const string k_MobileToggleName = k_MenuRoot + "Toggle Mobile";
const string k_IOSToggleName = k_MenuRoot + "Toggle iOS";
const string k_AndroidToggleName = k_MenuRoot + "Toggle Android";
const string k_DesktopToggleName = k_MenuRoot + "Toggle Desktop";
const string k_MacOSToggleName = k_MenuRoot + "Toggle MacOS";
const string k_WindowsToggleName = k_MenuRoot + "Toggle Windows";
const string k_DisableProjectIDToggleName = k_MenuRoot + "Skip Project ID Check"; // double negative in the name since menu is unchecked by default
const string k_SkipAutoDeleteToggleName = k_MenuRoot + "Skip Auto Delete Builds";
const int k_MenuGroupingBuild = 0; // to add separator in menus
const int k_MenuGroupingPlatforms = 11;
const int k_MenuGroupingOtherToggles = 22;
static BuildTarget s_CurrentEditorBuildTarget;
static BuildTargetGroup s_CurrentEditorBuildTargetGroup;
static int s_NbBuildsDone;
static string BuildPathRootDirectory => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds", "Playtest");
static string BuildPathDirectory(string platformName) => Path.Combine(BuildPathRootDirectory, platformName);
public static string BuildPath(string platformName) => Path.Combine(BuildPathDirectory(platformName), "BossRoomPlaytest");
[MenuItem(k_Build, false, k_MenuGroupingBuild)]
static async void Build()
{
s_NbBuildsDone = 0;
bool buildiOS = Menu.GetChecked(k_IOSToggleName);
bool buildAndroid = Menu.GetChecked(k_AndroidToggleName);
bool buildMacOS = Menu.GetChecked(k_MacOSToggleName);
bool buildWindows = Menu.GetChecked(k_WindowsToggleName);
bool skipAutoDelete = Menu.GetChecked(k_SkipAutoDeleteToggleName);
Debug.Log($"Starting build: buildiOS?:{buildiOS} buildAndroid?:{buildAndroid} buildMacOS?:{buildMacOS} buildWindows?:{buildWindows}");
if (string.IsNullOrEmpty(CloudProjectSettings.projectId) && !Menu.GetChecked(k_DisableProjectIDToggleName))
{
string errorMessage = $"Project ID was supposed to be setup and wasn't, make sure to set it up or disable project ID check with the [{k_DisableProjectIDToggleName}] menu";
EditorUtility.DisplayDialog("Error Custom Build", errorMessage, "ok");
throw new Exception(errorMessage);
}
SaveCurrentBuildTarget();
try
{
// deleting so we don't end up testing on outdated builds if there's a build failure
if (!skipAutoDelete) DeleteBuilds();
if (buildiOS) await BuildPlayerUtilityAsync(BuildTarget.iOS, "", true);
if (buildAndroid) await BuildPlayerUtilityAsync(BuildTarget.Android, ".apk", true); // there's the possibility of an error where it
// complains about NDK missing. Building manually on android then trying again seems to work? Can't find anything on this.
if (buildMacOS) await BuildPlayerUtilityAsync(BuildTarget.StandaloneOSX, ".app", true);
if (buildWindows) await BuildPlayerUtilityAsync(BuildTarget.StandaloneWindows64, ".exe", true);
}
catch
{
EditorUtility.DisplayDialog("Exception while building", "See console for details", "ok");
throw;
}
finally
{
Debug.Log($"Count builds done: {s_NbBuildsDone}");
RestoreBuildTarget();
}
}
[MenuItem(k_Build, true)]
static bool CanBuild()
{
return Menu.GetChecked(k_IOSToggleName) ||
Menu.GetChecked(k_AndroidToggleName) ||
Menu.GetChecked(k_MacOSToggleName) ||
Menu.GetChecked(k_WindowsToggleName);
}
static void RestoreBuildTarget()
{
Debug.Log($"restoring editor to initial build target {s_CurrentEditorBuildTarget}");
EditorUserBuildSettings.SwitchActiveBuildTarget(s_CurrentEditorBuildTargetGroup, s_CurrentEditorBuildTarget);
}
static void SaveCurrentBuildTarget()
{
s_CurrentEditorBuildTarget = EditorUserBuildSettings.activeBuildTarget;
s_CurrentEditorBuildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
}
[MenuItem(k_AllToggleName, false, k_MenuGroupingPlatforms)]
static void ToggleAll()
{
var newValue = ToggleMenu(k_AllToggleName);
ToggleMenu(k_DesktopToggleName, newValue);
ToggleMenu(k_MacOSToggleName, newValue);
ToggleMenu(k_WindowsToggleName, newValue);
ToggleMenu(k_MobileToggleName, newValue);
ToggleMenu(k_IOSToggleName, newValue);
ToggleMenu(k_AndroidToggleName, newValue);
}
[MenuItem(k_MobileToggleName, false, k_MenuGroupingPlatforms)]
static void ToggleMobile()
{
var newValue = ToggleMenu(k_MobileToggleName);
ToggleMenu(k_IOSToggleName, newValue);
ToggleMenu(k_AndroidToggleName, newValue);
}
[MenuItem(k_IOSToggleName, false, k_MenuGroupingPlatforms)]
static void ToggleiOS()
{
ToggleMenu(k_IOSToggleName);
}
[MenuItem(k_AndroidToggleName, false, k_MenuGroupingPlatforms)]
static void ToggleAndroid()
{
ToggleMenu(k_AndroidToggleName);
}
[MenuItem(k_DesktopToggleName, false, k_MenuGroupingPlatforms)]
static void ToggleDesktop()
{
var newValue = ToggleMenu(k_DesktopToggleName);
ToggleMenu(k_MacOSToggleName, newValue);
ToggleMenu(k_WindowsToggleName, newValue);
}
[MenuItem(k_MacOSToggleName, false, k_MenuGroupingPlatforms)]
static void ToggleMacOS()
{
ToggleMenu(k_MacOSToggleName);
}
[MenuItem(k_WindowsToggleName, false, k_MenuGroupingPlatforms)]
static void ToggleWindows()
{
ToggleMenu(k_WindowsToggleName);
}
[MenuItem(k_DisableProjectIDToggleName, false, k_MenuGroupingOtherToggles)]
static void ToggleProjectID()
{
ToggleMenu(k_DisableProjectIDToggleName);
}
[MenuItem(k_SkipAutoDeleteToggleName, false, k_MenuGroupingOtherToggles)]
static void ToggleAutoDelete()
{
ToggleMenu(k_SkipAutoDeleteToggleName);
}
static bool ToggleMenu(string menuName, bool? valueToSet = null)
{
bool toSet = !Menu.GetChecked(menuName);
if (valueToSet != null)
{
toSet = valueToSet.Value;
}
Menu.SetChecked(menuName, toSet);
return toSet;
}
static async Task BuildPlayerUtilityAsync(BuildTarget buildTarget = BuildTarget.NoTarget, string buildPathExtension = null, bool buildDebug = false)
{
s_NbBuildsDone++;
Debug.Log($"Starting build for {buildTarget.ToString()}");
await Task.Delay(100); // skipping some time to make sure debug logs are flushed before we build
var buildPathToUse = BuildPath(buildTarget.ToString());
buildPathToUse += buildPathExtension;
var buildPlayerOptions = new BuildPlayerOptions();
List scenesToInclude = new List();
foreach (var scene in EditorBuildSettings.scenes)
{
if (scene.enabled)
{
scenesToInclude.Add(scene.path);
}
}
buildPlayerOptions.scenes = scenesToInclude.ToArray();
buildPlayerOptions.locationPathName = buildPathToUse;
buildPlayerOptions.target = buildTarget;
var buildOptions = BuildOptions.None;
if (buildDebug)
{
buildOptions |= BuildOptions.Development;
}
buildOptions |= BuildOptions.StrictMode;
buildPlayerOptions.options = buildOptions;
BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
BuildSummary summary = report.summary;
if (summary.result == BuildResult.Succeeded)
{
Debug.Log($"Build succeeded: {summary.totalSize} bytes at {summary.outputPath}");
}
else
{
string debugString = buildDebug ? "debug" : "release";
throw new Exception($"Build failed for {debugString}:{buildTarget}! {report.summary.totalErrors} errors");
}
}
[MenuItem(k_DeleteBuilds, false, k_MenuGroupingBuild)]
public static void DeleteBuilds()
{
if (Directory.Exists(BuildPathRootDirectory))
{
Directory.Delete(BuildPathRootDirectory, recursive: true);
Debug.Log($"deleted {BuildPathRootDirectory}");
}
else
{
Debug.Log($"Build directory does not exist ({BuildPathRootDirectory}). No cleanup to do");
}
}
}