您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
589 行
22 KiB
589 行
22 KiB
using System.Linq;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEditor;
|
|
using UnityEditor.Build.Reporting;
|
|
|
|
public class BuildFrontend : EditorWindow
|
|
{
|
|
public const int CreateAssetMenuPriority = 5801;
|
|
public const int WindowMenuPriority = 203;
|
|
|
|
[MenuItem("File/Build Frontend %&B", priority = WindowMenuPriority)]
|
|
|
|
static void OpenWindow()
|
|
{
|
|
var window = GetWindow<BuildFrontend>();
|
|
window.PopulateAssets();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
titleContent = Contents.windowTitle;
|
|
PopulateAssets();
|
|
}
|
|
|
|
[SerializeField]
|
|
Dictionary<BuildTemplate, BuildReport> Reports = new Dictionary<BuildTemplate, BuildReport>();
|
|
string reportText = string.Empty;
|
|
|
|
private void OnGUI()
|
|
{
|
|
System.Action nextAction = null;
|
|
|
|
using(new GUILayout.HorizontalScope(GUILayout.Height(88)))
|
|
{
|
|
var rect = GUILayoutUtility.GetRect(88,88, Styles.Icon, GUILayout.Width(88));
|
|
GUI.DrawTexture(rect, Contents.icon);
|
|
using(new GUILayout.VerticalScope())
|
|
{
|
|
GUILayout.Space(8);
|
|
GUILayout.Label(Contents.title, Styles.Title);
|
|
GUILayout.FlexibleSpace();
|
|
DrawProgressBar();
|
|
GUILayout.Space(8);
|
|
}
|
|
GUILayout.Space(8);
|
|
|
|
using (new GUILayout.VerticalScope(GUILayout.Width(116)))
|
|
{
|
|
GUILayout.Space(8);
|
|
if(GUILayout.Button("Build All", Styles.BuildButton, GUILayout.Height(32)))
|
|
{
|
|
// Run Build
|
|
nextAction = DoAllBuild;
|
|
}
|
|
|
|
BuildTemplate template = (Selection.activeObject as BuildTemplate);
|
|
GUILayout.Space(2);
|
|
using (new GUILayout.HorizontalScope())
|
|
{
|
|
EditorGUI.BeginDisabledGroup(template == null);
|
|
if (GUILayout.Button("Build Selected", Styles.MiniButtonLeft))
|
|
{
|
|
nextAction = () =>
|
|
{
|
|
var report = template.DoBuild();
|
|
if (report != null)
|
|
Reports[template] = report;
|
|
|
|
m_SelectedReport = report;
|
|
Repaint();
|
|
};
|
|
}
|
|
if(GUILayout.Button("+ Run", Styles.MiniButtonRight, GUILayout.Width(48)))
|
|
{
|
|
nextAction = () =>
|
|
{
|
|
var report = template.DoBuild(true);
|
|
if (report != null)
|
|
Reports[template] = report;
|
|
|
|
m_SelectedReport = report;
|
|
Repaint();
|
|
};
|
|
}
|
|
}
|
|
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
EditorGUI.BeginDisabledGroup(template == null || !template.canRunBuild);
|
|
if (GUILayout.Button("Run Last Build", Styles.MiniButton))
|
|
{
|
|
nextAction = () =>
|
|
{
|
|
template.RunBuild();
|
|
EditorUtility.ClearProgressBar();
|
|
Repaint();
|
|
};
|
|
}
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
}
|
|
GUILayout.Space(8);
|
|
|
|
}
|
|
|
|
using(new GUILayout.HorizontalScope(EditorStyles.toolbar))
|
|
{
|
|
if(GUILayout.Button("Refresh", EditorStyles.toolbarButton))
|
|
{
|
|
PopulateAssets();
|
|
}
|
|
GUILayout.FlexibleSpace();
|
|
}
|
|
|
|
using (new GUILayout.HorizontalScope())
|
|
{
|
|
DrawTemplateList();
|
|
DrawReport();
|
|
}
|
|
|
|
if (nextAction != null)
|
|
{
|
|
var selected = Selection.activeObject;
|
|
nextAction.Invoke();
|
|
Selection.activeObject = selected;
|
|
}
|
|
|
|
}
|
|
|
|
void DrawReport()
|
|
{
|
|
reportScroll = EditorGUILayout.BeginScrollView(reportScroll, Styles.scrollView);
|
|
if (m_SelectedReport != null && Reports != null && Reports.ContainsValue(m_SelectedReport))
|
|
{
|
|
var template = Reports.First(o => o.Value == m_SelectedReport).Key;
|
|
FormatReportGUI(template, m_SelectedReport);
|
|
}
|
|
else
|
|
{
|
|
EditorGUILayout.HelpBox("No Build report has been generated yet, please build this template first", MessageType.Info);
|
|
}
|
|
EditorGUILayout.EndScrollView();
|
|
|
|
}
|
|
|
|
void DrawProgressBar()
|
|
{
|
|
GUI.backgroundColor = Color.gray;
|
|
using(new GUILayout.HorizontalScope(Styles.progressBarItem, GUILayout.Height(24), GUILayout.ExpandWidth(true)))
|
|
{
|
|
foreach(var cat in m_BuildTemplates)
|
|
{
|
|
foreach(var template in cat.Value)
|
|
{
|
|
GUI.backgroundColor = new Color(.3f,.3f,.3f,1.0f);
|
|
GUI.contentColor = Color.white;
|
|
if(!template.BuildEnabled)
|
|
{
|
|
GUI.backgroundColor = new Color(.4f,.4f,.4f,1.0f);
|
|
GUI.contentColor = new Color(1.0f,1.0f,1.0f, 0.5f);
|
|
}
|
|
else if(Reports.ContainsKey(template) && Reports[template] != null)
|
|
{
|
|
var report = Reports[template];
|
|
switch(report.summary.result)
|
|
{
|
|
case BuildResult.Succeeded:
|
|
GUI.backgroundColor = new Color(.15f,.5f,.05f,1.0f);
|
|
GUI.contentColor = new Color(0.3f,1.0f,0.1f, 1.0f);
|
|
break;
|
|
case BuildResult.Cancelled:
|
|
case BuildResult.Unknown:
|
|
case BuildResult.Failed:
|
|
GUI.backgroundColor = new Color(.5f,.05f,.15f,1.0f);
|
|
GUI.contentColor = new Color(1.0f,0.1f,0.3f, 1.0f);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
GUILayout.Label(template.name, Styles.progressBarItem, GUILayout.ExpandHeight(true));
|
|
}
|
|
}
|
|
}
|
|
GUI.contentColor = Color.white;
|
|
GUI.backgroundColor = Color.white;
|
|
}
|
|
|
|
void DoAllBuild()
|
|
{
|
|
try
|
|
{
|
|
foreach(var cat in m_BuildTemplates)
|
|
{
|
|
foreach(var template in cat.Value)
|
|
{
|
|
EditorUtility.DisplayProgressBar("Build Frontend", $"Building {template.name} ...", 1.0f);
|
|
if(template.BuildEnabled)
|
|
{
|
|
var Report = template.DoBuild();
|
|
Reports[template] = Report;
|
|
m_SelectedReport = Report;
|
|
Repaint();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
EditorUtility.ClearProgressBar();
|
|
}
|
|
}
|
|
|
|
Vector2 templateScroll = Vector2.zero;
|
|
Vector2 reportScroll = Vector2.zero;
|
|
BuildReport m_SelectedReport;
|
|
|
|
void DrawTemplateList()
|
|
{
|
|
templateScroll = GUILayout.BeginScrollView(templateScroll, false, true, GUILayout.Width(240));
|
|
|
|
using (new GUILayout.VerticalScope(EditorStyles.label))
|
|
{
|
|
foreach (var catKVP in m_BuildTemplates)
|
|
{
|
|
EditorGUILayout.LabelField(catKVP.Key == string.Empty? "General": catKVP.Key, EditorStyles.boldLabel);
|
|
|
|
foreach (var template in catKVP.Value)
|
|
{
|
|
// Draw Selected background box
|
|
if(template == CurrentTemplate)
|
|
{
|
|
Rect r = GUILayoutUtility.GetLastRect();
|
|
Vector2 pos = r.position;
|
|
pos.y += 18;
|
|
r.position = pos;
|
|
r.height += 2;
|
|
float gray = EditorGUIUtility.isProSkin? 1: 0;
|
|
EditorGUI.DrawRect(r, new Color(gray, gray, gray, 0.1f));
|
|
}
|
|
|
|
using (new GUILayout.HorizontalScope())
|
|
{
|
|
GUILayout.Space(16);
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
var enabled = GUILayout.Toggle(template.BuildEnabled,GUIContent.none, GUILayout.Width(24));
|
|
if(EditorGUI.EndChangeCheck())
|
|
{
|
|
template.BuildEnabled = enabled;
|
|
EditorUtility.SetDirty(template);
|
|
}
|
|
|
|
if (GUILayout.Button(template.Name != null && template.Name != string.Empty ? template.Name : template.name, template == CurrentTemplate? Styles.SelectedProfile : EditorStyles.label))
|
|
{
|
|
if (Reports.ContainsKey(template) && Reports[template] != null)
|
|
{
|
|
m_SelectedReport = Reports[template];
|
|
}
|
|
else
|
|
{
|
|
m_SelectedReport = null;
|
|
GUILayout.Label("Build has not been run yet");
|
|
}
|
|
|
|
CurrentTemplate = template;
|
|
CurrentProfile = CurrentTemplate.Profile;
|
|
CurrentSceneList = CurrentTemplate.SceneList;
|
|
Selection.activeObject = template;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
GUILayout.Space(16);
|
|
}
|
|
}
|
|
EditorGUILayout.EndScrollView();
|
|
}
|
|
|
|
[SerializeField]
|
|
BuildTemplate CurrentTemplate;
|
|
[SerializeField]
|
|
BuildProfile CurrentProfile;
|
|
[SerializeField]
|
|
SceneList CurrentSceneList;
|
|
|
|
GenericMenu TemplateMenu;
|
|
GenericMenu ProfileMenu;
|
|
GenericMenu SceneListMenu;
|
|
|
|
Dictionary<string, List<BuildTemplate>> m_BuildTemplates;
|
|
List<BuildProfile> m_BuildProfiles;
|
|
List<SceneList> m_SceneLists;
|
|
|
|
void PopulateAssets()
|
|
{
|
|
var buildTemplates = AssetDatabase.FindAssets("t:BuildTemplate");
|
|
var buildProfiles = AssetDatabase.FindAssets("t:BuildProfile");
|
|
var sceneLists = AssetDatabase.FindAssets("t:SceneList");
|
|
|
|
m_BuildTemplates = new Dictionary<string, List<BuildTemplate>>();
|
|
m_BuildProfiles = new List<BuildProfile>();
|
|
m_SceneLists = new List<SceneList>();
|
|
|
|
TemplateMenu = new GenericMenu();
|
|
foreach (var templateGUID in buildTemplates)
|
|
{
|
|
string templatePath = AssetDatabase.GUIDToAssetPath(templateGUID);
|
|
BuildTemplate template = (BuildTemplate)AssetDatabase.LoadAssetAtPath(templatePath, typeof(BuildTemplate));
|
|
if (!m_BuildTemplates.ContainsKey(template.Category))
|
|
m_BuildTemplates.Add(template.Category, new List<BuildTemplate>());
|
|
|
|
m_BuildTemplates[template.Category].Add(template);
|
|
|
|
TemplateMenu.AddItem(new GUIContent(template.MenuEntry), false, MenuSetTemplate, template);
|
|
}
|
|
|
|
ProfileMenu = new GenericMenu();
|
|
foreach (var profileGUID in buildProfiles)
|
|
{
|
|
string profilePath = AssetDatabase.GUIDToAssetPath(profileGUID);
|
|
BuildProfile profile = (BuildProfile)AssetDatabase.LoadAssetAtPath(profilePath, typeof(BuildProfile));
|
|
m_BuildProfiles.Add(profile);
|
|
ProfileMenu.AddItem(new GUIContent(profile.MenuEntry), false, MenuSetProfile, profile);
|
|
}
|
|
|
|
SceneListMenu = new GenericMenu();
|
|
foreach (var sceneListGUID in sceneLists)
|
|
{
|
|
string sceneListPath = AssetDatabase.GUIDToAssetPath(sceneListGUID);
|
|
SceneList sceneList = (SceneList)AssetDatabase.LoadAssetAtPath(sceneListPath, typeof(SceneList));
|
|
m_SceneLists.Add(sceneList);
|
|
SceneListMenu.AddItem(new GUIContent(sceneList.MenuEntry), false, MenuSetSceneList, sceneList);
|
|
}
|
|
}
|
|
|
|
void MenuSetTemplate(object o)
|
|
{
|
|
CurrentTemplate = (BuildTemplate)o;
|
|
CurrentProfile = CurrentTemplate.Profile;
|
|
CurrentSceneList = CurrentTemplate.SceneList;
|
|
}
|
|
|
|
void MenuSetProfile(object o)
|
|
{
|
|
CurrentProfile = (BuildProfile)o;
|
|
if(CurrentTemplate != null && !CurrentTemplate.name.EndsWith("*"))
|
|
{
|
|
CurrentTemplate = Instantiate<BuildTemplate>(CurrentTemplate) as BuildTemplate;
|
|
CurrentTemplate.name += "*";
|
|
}
|
|
|
|
CurrentTemplate.Profile = CurrentProfile;
|
|
}
|
|
|
|
void MenuSetSceneList(object o)
|
|
{
|
|
CurrentSceneList = (SceneList)o;
|
|
if (CurrentTemplate != null && !CurrentTemplate.name.EndsWith("*"))
|
|
{
|
|
CurrentTemplate = Instantiate<BuildTemplate>(CurrentTemplate) as BuildTemplate;
|
|
CurrentTemplate.name += "*";
|
|
}
|
|
|
|
CurrentTemplate.SceneList = CurrentSceneList;
|
|
}
|
|
|
|
string FormatSize(ulong byteSize)
|
|
{
|
|
double size = byteSize;
|
|
if (size < 1024) // Bytes
|
|
{
|
|
return $"{size.ToString("F2")} bytes";
|
|
}
|
|
else if (size < 1024 * 1024) // KiloBytes
|
|
{
|
|
return $"{(size / 1024).ToString("F2")} KiB ({byteSize} bytes)";
|
|
}
|
|
else if (size < 1024 * 1024 * 1024) // Megabytes
|
|
{
|
|
return $"{(size / (1024*1024)).ToString("F2")} MiB ({byteSize} bytes)";
|
|
}
|
|
else // Gigabytes
|
|
{
|
|
return $"{(size / (1024 * 1024 * 1024)).ToString("F2")} GiB ({byteSize} bytes)";
|
|
}
|
|
}
|
|
|
|
void FormatReportGUI(BuildTemplate template, BuildReport report)
|
|
{
|
|
var summary = report.summary;
|
|
|
|
if(template != null)
|
|
GUILayout.Label($"{(template.Name == string.Empty? template.name : template.Name)} {(template.Category == string.Empty?"":$"({template.Category})")}", Styles.boldLabelLarge);
|
|
|
|
using (new GUILayout.HorizontalScope())
|
|
{
|
|
if (summary.result == BuildResult.Succeeded)
|
|
GUILayout.Label(Contents.buildSucceeded, GUILayout.Width(32));
|
|
else if (summary.result != BuildResult.Unknown)
|
|
GUILayout.Label(Contents.buildFailed, GUILayout.Width(32));
|
|
|
|
GUILayout.Label(summary.result.ToString(), Styles.boldLabelLarge);
|
|
}
|
|
|
|
GUILayout.Space(8);
|
|
GUILayout.Label("Total Build Time :" + summary.totalTime);
|
|
EditorGUILayout.LabelField($"Build Size : {FormatSize(summary.totalSize)} ");
|
|
|
|
if(summary.totalErrors > 0)
|
|
{
|
|
GUILayout.Label(new GUIContent($"{(int)summary.totalErrors} Errors", Contents.errorIconSmall.image));
|
|
}
|
|
|
|
if(summary.totalWarnings > 0)
|
|
{
|
|
GUILayout.Label(new GUIContent($"{(int)summary.totalWarnings} Warnings", Contents.warnIconSmall.image));
|
|
}
|
|
|
|
EditorGUILayout.TextField("Output Path", summary.outputPath);
|
|
|
|
if(report.strippingInfo != null)
|
|
{
|
|
GUILayout.Space(8);
|
|
|
|
GUILayout.Label("Included Modules", EditorStyles.boldLabel);
|
|
var modules = report.strippingInfo.includedModules;
|
|
foreach(var module in modules)
|
|
{
|
|
EditorGUILayout.LabelField(module, EditorStyles.foldout);
|
|
}
|
|
}
|
|
|
|
GUILayout.Space(8);
|
|
GUILayout.Label("Build Steps", EditorStyles.boldLabel);
|
|
var steps = report.steps;
|
|
foreach(var step in steps)
|
|
{
|
|
string prefName = $"BuildFrontend.Foldout'{step.name}'";
|
|
|
|
using (new GUILayout.HorizontalScope())
|
|
{
|
|
if (step.messages.Any(o => o.type == LogType.Error || o.type == LogType.Assert || o.type == LogType.Exception))
|
|
GUILayout.Label(Contents.errorIconSmall, Styles.Icon, GUILayout.Width(16));
|
|
else if (step.messages.Any(o => o.type == LogType.Warning))
|
|
GUILayout.Label(Contents.warnIconSmall, Styles.Icon, GUILayout.Width(16));
|
|
else
|
|
GUILayout.Label(Contents.successIcon, Styles.Icon, GUILayout.Width(16));
|
|
|
|
if (step.messages.Length > 0)
|
|
{
|
|
bool pref = EditorPrefs.GetBool(prefName, false);
|
|
|
|
bool newPref = EditorGUILayout.Foldout(pref, step.name);
|
|
|
|
if (GUI.changed && pref != newPref)
|
|
EditorPrefs.SetBool(prefName, newPref);
|
|
}
|
|
else
|
|
{
|
|
EditorGUILayout.LabelField(step.name);
|
|
}
|
|
|
|
}
|
|
|
|
EditorGUI.indentLevel++;
|
|
if (EditorPrefs.GetBool(prefName, false))
|
|
{
|
|
foreach (var message in step.messages)
|
|
{
|
|
MessageType type = MessageType.Error;
|
|
if (message.type == LogType.Log)
|
|
type = MessageType.Info;
|
|
else if (message.type == LogType.Warning)
|
|
type = MessageType.Warning;
|
|
|
|
EditorGUILayout.HelpBox(message.content, type, true);
|
|
}
|
|
}
|
|
EditorGUI.indentLevel--;
|
|
|
|
GUILayout.Space(2);
|
|
}
|
|
GUILayout.Space(128);
|
|
}
|
|
|
|
static class Styles
|
|
{
|
|
public static GUIStyle BuildButton;
|
|
public static GUIStyle MiniButton;
|
|
public static GUIStyle MiniButtonLeft;
|
|
public static GUIStyle MiniButtonRight;
|
|
public static GUIStyle progressBarItem;
|
|
public static GUIStyle SelectedProfile;
|
|
public static GUIStyle Title;
|
|
public static GUIStyle Icon;
|
|
public static GUIStyle boldLabelLarge;
|
|
public static GUIStyle scrollView;
|
|
|
|
static Styles()
|
|
{
|
|
BuildButton = new GUIStyle(EditorStyles.miniButton);
|
|
BuildButton.fontSize = 14;
|
|
BuildButton.fixedHeight = 32;
|
|
|
|
MiniButton = new GUIStyle(EditorStyles.miniButton);
|
|
MiniButton.fontSize = 10;
|
|
|
|
MiniButtonLeft = new GUIStyle(EditorStyles.miniButtonLeft);
|
|
MiniButtonLeft.fontSize = 10;
|
|
|
|
MiniButtonRight = new GUIStyle(EditorStyles.miniButtonRight);
|
|
MiniButtonRight.fontSize = 10;
|
|
|
|
|
|
SelectedProfile = new GUIStyle(EditorStyles.label);
|
|
var pink = EditorGUIUtility.isProSkin? new Color(1.0f, 0.2f, 0.5f, 1.0f) : new Color(1.0f, 0.05f, 0.4f, 1.0f);
|
|
SelectedProfile.active.textColor = pink;
|
|
SelectedProfile.focused.textColor = pink;
|
|
SelectedProfile.hover.textColor = pink;
|
|
SelectedProfile.normal.textColor = pink;
|
|
|
|
Title = new GUIStyle(EditorStyles.label);
|
|
Title.fontSize = 18;
|
|
|
|
Icon = new GUIStyle(EditorStyles.label);
|
|
|
|
Icon.padding = new RectOffset();
|
|
Icon.margin = new RectOffset();
|
|
|
|
progressBarItem = new GUIStyle(EditorStyles.miniLabel);
|
|
progressBarItem.alignment = TextAnchor.MiddleCenter;
|
|
progressBarItem.margin = new RectOffset(0,0,0,0);
|
|
progressBarItem.padding = new RectOffset(0,0,0,0);
|
|
progressBarItem.wordWrap = true;
|
|
progressBarItem.onActive.background = Texture2D.whiteTexture;
|
|
progressBarItem.onFocused.background = Texture2D.whiteTexture;
|
|
progressBarItem.onHover.background = Texture2D.whiteTexture;
|
|
progressBarItem.onNormal.background = Texture2D.whiteTexture;
|
|
progressBarItem.active.background = Texture2D.whiteTexture;
|
|
progressBarItem.focused.background = Texture2D.whiteTexture;
|
|
progressBarItem.hover.background = Texture2D.whiteTexture;
|
|
progressBarItem.normal.background = Texture2D.whiteTexture;
|
|
|
|
boldLabelLarge = new GUIStyle(EditorStyles.boldLabel);
|
|
boldLabelLarge.fontSize = 16;
|
|
|
|
scrollView = new GUIStyle();
|
|
scrollView.padding = new RectOffset(8, 8, 8, 8);
|
|
|
|
}
|
|
}
|
|
static class Contents
|
|
{
|
|
public static GUIContent windowTitle;
|
|
public static GUIContent title;
|
|
public static GUIContent build = new GUIContent("Build");
|
|
public static GUIContent template = new GUIContent("Template:");
|
|
public static GUIContent profile = new GUIContent("Profile:");
|
|
public static GUIContent sceneList = new GUIContent("Scene List:");
|
|
public static Texture icon;
|
|
|
|
public static GUIContent buildSucceeded = EditorGUIUtility.IconContent("Collab.BuildSucceeded");
|
|
public static GUIContent buildFailed = EditorGUIUtility.IconContent("Collab.BuildFailed");
|
|
|
|
public static GUIContent successIcon = EditorGUIUtility.IconContent("Collab");
|
|
public static GUIContent failIcon = EditorGUIUtility.IconContent("CollabError");
|
|
|
|
public static GUIContent errorIcon = EditorGUIUtility.IconContent("console.erroricon");
|
|
public static GUIContent errorIconSmall = EditorGUIUtility.IconContent("console.erroricon.sml");
|
|
|
|
public static GUIContent infoIcon = EditorGUIUtility.IconContent("console.infoicon");
|
|
public static GUIContent infoIconSmall = EditorGUIUtility.IconContent("console.infoicon.sml");
|
|
|
|
public static GUIContent warnIcon = EditorGUIUtility.IconContent("console.warnicon");
|
|
public static GUIContent warnIconSmall = EditorGUIUtility.IconContent("console.warnicon.sml");
|
|
|
|
static Contents()
|
|
{
|
|
icon = AssetDatabase.LoadAssetAtPath<Texture>("Packages/net.peeweek.build-frontend/Editor/Icons/BuildFrontend.png");
|
|
var titleIcon = AssetDatabase.LoadAssetAtPath<Texture>($"Packages/net.peeweek.build-frontend/Editor/Icons/BuildFrontendTab{(EditorGUIUtility.isProSkin?"":"Personal")}.png");
|
|
windowTitle = new GUIContent("Build Frontend", titleIcon);
|
|
title = new GUIContent("Build Frontend");
|
|
}
|
|
|
|
}
|
|
}
|