12using System.Diagnostics;
13using System.Text.RegularExpressions;
15using System.Reflection;
26 using System.Collections.Generic;
28 [CreateAssetMenu(fileName =
"Build", menuName =
"Build Definition", order = 201)]
30 private const string BUILD_EXPORT_FOLDER_KEY =
"LeapBuildDefExportFolder";
31 private const string DEFAULT_BUILD_NAME =
"Build.asset";
37 [
DisableIf(
"useLocalBuildFolderPath", isEqualTo:
false)]
38 [Tooltip(
"Relative to Application.dataPath, AKA the Assets folder.")]
42 [Tooltip(
"The options to enable for this build.")]
45 protected BuildOptions _options = BuildOptions.None;
47 [Tooltip(
"If disabled, the editor's current Player Settings will be used. " +
48 "Not all Player Settings are supported. Any unspecified settings are " +
49 "inherited from the editor's current Player Settings.")]
51 protected bool _useSpecificPlayerSettings =
true;
54 public struct BuildPlayerSettings {
55 #if UNITY_2018_3_OR_NEWER
56 public FullScreenMode fullScreenMode;
58 public int defaultScreenWidth;
59 public int defaultScreenHeight;
60 public bool resizableWindow;
61 #if !UNITY_2019_1_OR_NEWER
62 public ResolutionDialogSetting resolutionDialogSetting;
64 public static BuildPlayerSettings Default() {
65 return new BuildPlayerSettings() {
66 #if UNITY_2018_3_OR_NEWER
67 fullScreenMode = FullScreenMode.Windowed,
69 defaultScreenWidth = 800,
70 defaultScreenHeight = 500,
71 resizableWindow =
true,
72 #if !UNITY_2019_1_OR_NEWER
73 resolutionDialogSetting = ResolutionDialogSetting.HiddenByDefault
78 [Tooltip(
"Only used when Use Specific Player Settings is true. " +
79 "Not all Player Settings are supported. Any unspecified settings " +
80 "are inherited from the editor's current Player Settings.")]
82 protected BuildPlayerSettings _playerSettings =
83 BuildPlayerSettings.Default();
85 [Tooltip(
"The scenes that should be included in this build, " +
"" +
86 "in the order they should be included.")]
88 protected SceneAsset[] _scenes;
90 [Tooltip(
"The build targets to use for this build definition.")]
92 protected BuildTarget[] _targets = { BuildTarget.StandaloneWindows64 };
94 public BuildDefinition() {
98 public static void BuildFromGUID(
string guid) {
99 var path = AssetDatabase.GUIDToAssetPath(guid);
100 var def = AssetDatabase.LoadAssetAtPath<BuildDefinition>(path);
104 public enum ExitCode {
105 NoExportFolderSpecified = 5,
106 ExceptionInPostBuildMethod = 6
109 public bool crashWithCodeIfBuildFails =
false;
111 public void Build(
string overrideExportFolder =
null,
112 bool crashWithCodeOnFail =
false,
bool openFileWindowWhenDone =
true)
114 crashWithCodeIfBuildFails = crashWithCodeOnFail;
117 if (overrideExportFolder !=
null) {
118 exportFolder = overrideExportFolder;
125 exportFolder =
new DirectoryInfo(exportFolder).FullName;
126 }
catch (Exception) {
128 "localBuildFolderPath was used and directory " + exportFolder +
129 " could not be created.");
133 exportFolder =
new DirectoryInfo(exportFolder).FullName;
136 else if (!TryGetPackageExportFolder(out exportFolder, promptIfNotDefined:
true)) {
138 if (crashWithCodeOnFail) {
139 EditorApplication.Exit((
int)ExitCode.NoExportFolderSpecified);
146 if (tryGetGitCommitHash(out hash)) {
147 exportFolder = Path.Combine(exportFolder,
DefinitionName +
"_" + hash);
149 UnityEngine.Debug.LogWarning(
"Failed to get git hash.");
154 string fullExecutablePath = Path.Combine(
159 var buildOptions =
new BuildPlayerOptions() {
160 scenes = _scenes.Where(s => s !=
null).
161 Select(s => AssetDatabase.GetAssetPath(s)).
166 foreach (var target
in _targets) {
167 buildOptions.target = target;
168 buildOptions.locationPathName = fullExecutablePath +
169 getFileSuffix(target);
171 if (_useSpecificPlayerSettings) {
172 #if UNITY_2018_3_OR_NEWER
173 var origFullscreenMode = PlayerSettings.fullScreenMode;
175 var origDefaultWidth = PlayerSettings.defaultScreenWidth;
176 var origDefaultHeight = PlayerSettings.defaultScreenHeight;
177 var origResizableWindow = PlayerSettings.resizableWindow;
178 #if !UNITY_2019_1_OR_NEWER
179 var origResDialogSetting = PlayerSettings.displayResolutionDialog;
182 #if UNITY_2018_3_OR_NEWER
183 PlayerSettings.fullScreenMode = _playerSettings.fullScreenMode;
185 PlayerSettings.defaultScreenWidth = _playerSettings.defaultScreenWidth;
186 PlayerSettings.defaultScreenHeight =
187 _playerSettings.defaultScreenHeight;
188 PlayerSettings.resizableWindow = _playerSettings.resizableWindow;
189 #if !UNITY_2019_1_OR_NEWER
190 PlayerSettings.displayResolutionDialog =
191 _playerSettings.resolutionDialogSetting;
194 callPreBuildExtensions(fullBuildPath, crashWithCodeOnFail);
195 BuildPipeline.BuildPlayer(buildOptions);
196 callPostBuildExtensions(fullBuildPath, crashWithCodeOnFail);
199 #if UNITY_2018_3_OR_NEWER
200 PlayerSettings.fullScreenMode = origFullscreenMode;
202 PlayerSettings.defaultScreenWidth = origDefaultWidth;
203 PlayerSettings.defaultScreenHeight = origDefaultHeight;
204 PlayerSettings.resizableWindow = origResizableWindow;
205 #if !UNITY_2019_1_OR_NEWER
206 PlayerSettings.displayResolutionDialog = origResDialogSetting;
211 callPreBuildExtensions(fullBuildPath, crashWithCodeOnFail);
212 BuildPipeline.BuildPlayer(buildOptions);
213 callPostBuildExtensions(fullBuildPath, crashWithCodeOnFail);
216 if (_options.HasFlag(BuildOptions.EnableHeadlessMode)) {
223 var headlessModeBatPath = Path.Combine(Path.Combine(exportFolder,
225 File.WriteAllText(headlessModeBatPath, text);
229 if (openFileWindowWhenDone) {
230 Process.Start(exportFolder);
234 private void callPreBuildExtensions(
string exportFolder,
235 bool crashWithCodeOnFail =
false) {
237 foreach (var preBuildKeyValue
in BuildExtensions.preBuildExtensionMethods) {
238 var attribute = preBuildKeyValue.Key;
239 var methodInfo = preBuildKeyValue.Value;
241 var methodArgs =
new object[] {
this, exportFolder };
242 methodInfo.Invoke(
null, methodArgs);
245 catch (System.Exception e) {
246 Debug.LogError(
"Caught exception while calling a pre-build " +
247 "extension methods: " + e.ToString());
248 if (crashWithCodeOnFail) {
249 EditorApplication.Exit((
int)ExitCode.ExceptionInPostBuildMethod);
254 private void callPostBuildExtensions(
string exportFolder,
255 bool crashWithCodeOnFail =
false)
258 foreach (var postBuildKeyValue
in BuildExtensions.postBuildExtensionMethods) {
259 var attribute = postBuildKeyValue.Key;
260 var methodInfo = postBuildKeyValue.Value;
261 var methodArgs =
new object[] {
this, exportFolder };
262 methodInfo.Invoke(
null, methodArgs);
265 catch (System.Exception e) {
266 Debug.LogError(
"Caught exception while calling post-build " +
267 "extension methods: " + e.ToString());
268 if (crashWithCodeOnFail) {
269 EditorApplication.Exit((
int)ExitCode.ExceptionInPostBuildMethod);
274 private static string getFileSuffix(BuildTarget target) {
276 case BuildTarget.StandaloneWindows:
277 case BuildTarget.StandaloneWindows64:
279 case BuildTarget.Android:
281 case BuildTarget.StandaloneLinux64:
288 private static bool tryGetGitCommitHash(out
string hash) {
290 Process process =
new Process();
292 ProcessStartInfo startInfo =
new ProcessStartInfo() {
293 WindowStyle = ProcessWindowStyle.Hidden,
294 FileName =
"cmd.exe",
295 Arguments =
"/C git log -1",
296 WorkingDirectory =
Directory.GetParent(Application.dataPath).FullName,
297 RedirectStandardOutput =
true,
298 CreateNoWindow =
true,
299 UseShellExecute =
false
302 process.StartInfo = startInfo;
305 string result = process.StandardOutput.ReadToEnd();
307 var match = Regex.Match(result,
@"^commit (\w{40})");
309 hash = match.Groups[1].Value;
315 }
catch (Exception e) {
322 [MenuItem(
"Build/All Apps", priority = 1)]
323 public static void BuildAll() {
324 foreach (var item
in EditorResources.FindAllAssetsOfType<BuildDefinition>()) {
329 [MenuItem(
"Build/All Apps", priority = 1, validate =
true)]
330 public static bool ValidateBuildAll() {
331 return EditorResources.FindAllAssetsOfType<BuildDefinition>().Length > 0;
335 [System.Serializable]
338 [Header(
"Extension Data")]
343 [AttributeUsage(validOn: AttributeTargets.Method)]
346 [AttributeUsage(validOn: AttributeTargets.Method)]
349 public static class BuildExtensions {
351 private static Dictionary<Attribute, MethodInfo>
352 _backingPreBuildExtensionMethods =
null;
353 public static Dictionary<Attribute, MethodInfo> preBuildExtensionMethods {
355 if (_backingPreBuildExtensionMethods ==
null) {
356 _backingPreBuildExtensionMethods =
357 new Dictionary<Attribute, MethodInfo>();
359 return _backingPreBuildExtensionMethods;
363 private static Dictionary<Attribute, MethodInfo>
364 _backingPostBuildExtensionMethods =
null;
365 public static Dictionary<Attribute, MethodInfo> postBuildExtensionMethods {
367 if (_backingPostBuildExtensionMethods ==
null) {
368 _backingPostBuildExtensionMethods =
369 new Dictionary<Attribute, MethodInfo>();
371 return _backingPostBuildExtensionMethods;
378 private static void InitializeOnLoad() {
BuildExtensionData extensionData
string localBuildFolderPath
bool _trySuffixWithGitHash
bool useLocalBuildFolderPath
In order to have this class be serialized, you will always need to create your own non-generic versio...
System.Diagnostics.Debug DiagDebug