Skip to content

Instantly share code, notes, and snippets.

@msklywenn
Last active May 18, 2022 14:32
Show Gist options
  • Save msklywenn/a3022c730561ef34c5bd14278ac14f1f to your computer and use it in GitHub Desktop.
Save msklywenn/a3022c730561ef34c5bd14278ac14f1f to your computer and use it in GitHub Desktop.
Unity Batch Builder
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyright (C) 2004 Sam Hocevar <[email protected]>
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
#define FMOD // comment if you're not using fmod
using UnityEditor;
using UnityEngine;
using System.IO;
using System;
class BatchBuilderSettings : ScriptableObject
{
[Serializable]
public struct Settings
{
public string Name;
public bool Active;
public BuildTarget Target;
public BuildTargetGroup Group;
public string Symbols;
public string Path;
public string Filename;
public UnityEditor.AnimatedValues.AnimBool Editing;
}
public Settings[] Builds = new Settings[0];
[Serializable]
public struct Symbol
{
public bool Active;
public string Name;
}
public Symbol[] GeneralSymbols = new Symbol[0];
public bool Debug = false;
public bool LZ4Compression = true;
}
class BatchBuilder : EditorWindow
{
const string settingsPath = "Assets/Editor/Resources/BatchBuild.asset";
BatchBuilderSettings settings;
Vector2 scrollTargets;
Vector2 scrollSymbols;
enum Step
{
SetSymbols,
#if FMOD
AudioCopy,
#endif
Compilation,
Build
}
int building = -1;
int succeeded = 0;
int failed = 0;
Step step = Step.SetSymbols;
System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();
[MenuItem("Tools/Batch Builder")]
static void Open()
{
EditorWindow w = GetWindow<BatchBuilder>();
w.minSize = new Vector2(750, 320);
}
void OnEnable()
{
settings = AssetDatabase.LoadAssetAtPath<BatchBuilderSettings>(settingsPath);
if (settings == null)
{
settings = CreateInstance<BatchBuilderSettings>();
AssetDatabase.CreateAsset(settings, settingsPath);
AssetDatabase.SaveAssets();
}
EditorApplication.update += Update;
foreach (BatchBuilderSettings.Settings s in settings.Builds)
s.Editing.valueChanged.AddListener(Repaint);
}
void OnDisable()
{
EditorApplication.update -= Update;
}
void OnGUI()
{
GUI.enabled = building == -1;
GUILayout.Label("Targets", EditorStyles.boldLabel);
GUILayout.BeginHorizontal();
{
GUILayout.Label("Name", EditorStyles.boldLabel, GUILayout.Width(200));
GUILayout.FlexibleSpace();
GUILayout.Label("Directory", EditorStyles.boldLabel, GUILayout.Width(200));
GUILayout.Label("Filename", EditorStyles.boldLabel, GUILayout.Width(200));
GUILayout.Space(60); // width of edit, X
}
GUILayout.EndHorizontal();
EditorGUI.BeginChangeCheck();
{
Undo.RecordObject(settings, "Edit Batch Build Settings");
BatchBuilderSettings.Settings[] builds = settings.Builds;
scrollTargets = EditorGUILayout.BeginScrollView(scrollTargets);
{
for (int i = 0; i < builds.Length; i++)
{
bool deleted = false;
GUILayout.BeginHorizontal();
{
if (builds[i].Editing.target)
{
builds[i].Active = EditorGUILayout.Toggle(builds[i].Active, GUILayout.Width(15));
builds[i].Name = EditorGUILayout.TextField(builds[i].Name);
GUILayout.FlexibleSpace();
builds[i].Path = EditorGUILayout.TextField(builds[i].Path, GUILayout.Width(200));
builds[i].Filename = EditorGUILayout.TextField(builds[i].Filename, GUILayout.Width(200));
}
else
{
builds[i].Active = EditorGUILayout.ToggleLeft(builds[i].Name, builds[i].Active);
GUILayout.FlexibleSpace();
GUILayout.Label(builds[i].Path, GUILayout.Width(200));
GUILayout.Label(builds[i].Filename, GUILayout.Width(200));
}
bool wasEditing = builds[i].Editing.target;
builds[i].Editing.target = GUILayout.Toggle(builds[i].Editing.target, "Edit", "Button");
if (wasEditing && !builds[i].Editing.target)
GUI.FocusControl(string.Empty);
if (GUILayout.Button("X", GUILayout.Width(20)))
{
deleted = true;
ArrayUtility.RemoveAt(ref settings.Builds, i);
GUI.FocusControl(string.Empty);
i--;
}
}
GUILayout.EndHorizontal();
if (!deleted && builds[i].Editing.target)
{
EditorGUILayout.BeginFadeGroup(builds[i].Editing.faded);
{
builds[i].Target = (BuildTarget)EditorGUILayout.EnumPopup("Target", builds[i].Target);
builds[i].Group = (BuildTargetGroup)EditorGUILayout.EnumPopup("Group", builds[i].Group);
builds[i].Symbols = EditorGUILayout.TextField("Symbols", builds[i].Symbols);
}
EditorGUILayout.EndFadeGroup();
}
}
}
EditorGUILayout.EndScrollView();
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Add"))
{
ArrayUtility.Insert(ref settings.Builds, settings.Builds.Length,
new BatchBuilderSettings.Settings()
{
Name = "New Build",
Path = "Builds/New",
Filename = PlayerSettings.productName + ".exe",
Target = BuildTarget.StandaloneWindows64,
Group = BuildTargetGroup.Standalone,
Symbols = "TMPPRESENT",
Active = false,
Editing = new UnityEditor.AnimatedValues.AnimBool(true),
});
}
GUILayout.EndHorizontal();
GUILayout.Label("General Symbols", EditorStyles.boldLabel);
scrollSymbols = EditorGUILayout.BeginScrollView(scrollSymbols);
{
for (int i = 0; i < settings.GeneralSymbols.Length; i++)
{
GUILayout.BeginHorizontal();
{
settings.GeneralSymbols[i].Active = EditorGUILayout.Toggle(settings.GeneralSymbols[i].Active, GUILayout.Width(15));
settings.GeneralSymbols[i].Name = GUILayout.TextField(settings.GeneralSymbols[i].Name);
if (GUILayout.Button("X", GUILayout.Width(20)))
{
ArrayUtility.RemoveAt(ref settings.GeneralSymbols, i);
i--;
}
}
GUILayout.EndHorizontal();
}
}
EditorGUILayout.EndScrollView();
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Add"))
ArrayUtility.Insert(ref settings.GeneralSymbols, settings.GeneralSymbols.Length,
settings.GeneralSymbols[settings.GeneralSymbols.Length - 1]);
GUILayout.EndHorizontal();
GUILayout.Label("Options", EditorStyles.boldLabel);
GUILayout.BeginHorizontal();
{
settings.Debug = EditorGUILayout.ToggleLeft("Debug", settings.Debug);
settings.LZ4Compression = EditorGUILayout.ToggleLeft("LZ4 Compression", settings.LZ4Compression);
GUILayout.FlexibleSpace();
}
GUILayout.EndHorizontal();
}
if (EditorGUI.EndChangeCheck())
EditorUtility.SetDirty(settings);
if (building == -1)
{
if (GUILayout.Button("Build"))
{
timer.Start();
building = 0;
succeeded = 0;
failed = 0;
step = Step.SetSymbols;
}
}
else
{
GUI.enabled = true;
Rect rect = EditorGUILayout.GetControlRect(false, 20);
int totalSteps = Enum.GetNames(typeof(Step)).Length;
float progress = (building * totalSteps + (int)step) / (float)(settings.Builds.Length * totalSteps);
string text = string.Format("{0}: {1}", settings.Builds[building].Name, step);
EditorGUI.ProgressBar(rect, progress, text);
}
}
private void Update()
{
if (building >= 0)
{
Repaint();
if (settings.Builds[building].Active)
{
switch (step)
{
case Step.SetSymbols:
{
SetSymbols(settings.Builds[building]);
#if FMOD
step = Step.AudioCopy;
#else
step = Step.Compilation;
#endif
break;
}
#if FMOD
case Step.AudioCopy:
{
FMODUnity.EventManager.CopyToStreamingAssets();
step = Step.Compilation;
break;
}
#endif
case Step.Compilation:
{
if (!EditorApplication.isCompiling)
step = Step.Build;
break;
}
case Step.Build:
{
if (Build(settings.Builds[building]))
{
building = -1;
Debug.LogWarning("Builds canceled");
}
else
{
step = Step.SetSymbols;
if (building != -1)
building++;
}
break;
}
}
}
else
{
building++;
}
if (building >= settings.Builds.Length)
{
building = -1;
timer.Stop();
Debug.LogFormat("Builds complete: {0} succeeded, {1} failed ({2})", succeeded, failed, timer.Elapsed);
if (succeeded > 0)
{
EditorUtility.RevealInFinder("Builds");
EditorApplication.Beep();
}
}
}
}
void SetSymbols(BatchBuilderSettings.Settings build)
{
string extraSymbols = "";
foreach (BatchBuilderSettings.Symbol symbol in settings.GeneralSymbols)
if (symbol.Active)
extraSymbols += ";" + symbol.Name;
PlayerSettings.SetScriptingDefineSymbolsForGroup(build.Group, build.Symbols + extraSymbols);
}
bool Build(BatchBuilderSettings.Settings build)
{
if (Directory.Exists(build.Path))
Directory.Delete(build.Path, true);
Directory.CreateDirectory(build.Path);
string file = Path.Combine(build.Path, build.Filename);
BuildOptions options = BuildOptions.StrictMode;
if (settings.Debug)
options |= BuildOptions.Development | BuildOptions.ConnectWithProfiler;
else if (settings.LZ4Compression)
options |= BuildOptions.CompressWithLz4;
Debug.LogFormat("Starting build {0} ({1})", build.Name, PlayerSettings.GetScriptingDefineSymbolsForGroup(build.Group));
bool success = false, canceled = false;
try
{
string error = BuildPipeline.BuildPlayer(EditorBuildSettings.scenes, file, build.Target, options);
if (string.IsNullOrEmpty(error))
success = true;
if (!success)
{
if (error == "Building Player was cancelled")
canceled = true;
else
Debug.LogError("Error while building " + build.Name + ": " + error);
}
}
catch (Exception ex)
{
Debug.LogError("Error while building " + build.Name + ": " + ex.Message);
}
if (success)
succeeded++;
else
failed++;
return canceled;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment