Tanoda
BuildDefinition.cs
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (C) Ultraleap, Inc. 2011-2020. *
3 * *
4 * Use subject to the terms of the Apache License 2.0 available at *
5 * http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
6 * between Ultraleap and you, your company or other organization. *
7 ******************************************************************************/
8
9using System;
10using System.IO;
11using System.Linq;
12using System.Diagnostics;
13using System.Text.RegularExpressions;
14using UnityEngine;
15using System.Reflection;
16
17#if UNITY_EDITOR
18using UnityEditor;
19#endif
20
22 using Attributes;
23
24 using DiagDebug = System.Diagnostics.Debug;
25 using Debug = UnityEngine.Debug;
26 using System.Collections.Generic;
27
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";
32
33 [SerializeField]
34 protected bool _trySuffixWithGitHash = false;
35
36 public bool useLocalBuildFolderPath = false;
37 [DisableIf("useLocalBuildFolderPath", isEqualTo: false)]
38 [Tooltip("Relative to Application.dataPath, AKA the Assets folder.")]
39 public string localBuildFolderPath = "../Builds/";
40
41#if UNITY_EDITOR
42 [Tooltip("The options to enable for this build.")]
43 [EnumFlags]
44 [SerializeField]
45 protected BuildOptions _options = BuildOptions.None;
46
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.")]
50 [SerializeField]
51 protected bool _useSpecificPlayerSettings = true;
52
53 [System.Serializable]
54 public struct BuildPlayerSettings {
55 #if UNITY_2018_3_OR_NEWER
56 public FullScreenMode fullScreenMode;
57 #endif
58 public int defaultScreenWidth;
59 public int defaultScreenHeight;
60 public bool resizableWindow;
61 #if !UNITY_2019_1_OR_NEWER
62 public ResolutionDialogSetting resolutionDialogSetting;
63 #endif
64 public static BuildPlayerSettings Default() {
65 return new BuildPlayerSettings() {
66 #if UNITY_2018_3_OR_NEWER
67 fullScreenMode = FullScreenMode.Windowed,
68 #endif
69 defaultScreenWidth = 800,
70 defaultScreenHeight = 500,
71 resizableWindow = true,
72 #if !UNITY_2019_1_OR_NEWER
73 resolutionDialogSetting = ResolutionDialogSetting.HiddenByDefault
74 #endif
75 };
76 }
77 }
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.")]
81 [SerializeField]
82 protected BuildPlayerSettings _playerSettings =
83 BuildPlayerSettings.Default();
84
85 [Tooltip("The scenes that should be included in this build, " + "" +
86 "in the order they should be included.")]
87 [SerializeField]
88 protected SceneAsset[] _scenes;
89
90 [Tooltip("The build targets to use for this build definition.")]
91 [SerializeField]
92 protected BuildTarget[] _targets = { BuildTarget.StandaloneWindows64 };
93
94 public BuildDefinition() {
95 _definitionName = "Build";
96 }
97
98 public static void BuildFromGUID(string guid) {
99 var path = AssetDatabase.GUIDToAssetPath(guid);
100 var def = AssetDatabase.LoadAssetAtPath<BuildDefinition>(path);
101 def.Build();
102 }
103
104 public enum ExitCode {
105 NoExportFolderSpecified = 5,
106 ExceptionInPostBuildMethod = 6
107 }
108
109 public bool crashWithCodeIfBuildFails = false;
110
111 public void Build(string overrideExportFolder = null,
112 bool crashWithCodeOnFail = false, bool openFileWindowWhenDone = true)
113 {
114 crashWithCodeIfBuildFails = crashWithCodeOnFail;
115
116 string exportFolder;
117 if (overrideExportFolder != null) {
118 exportFolder = overrideExportFolder;
119 }
120 else if (useLocalBuildFolderPath) {
121 exportFolder = Path.Combine(Application.dataPath, localBuildFolderPath);
122 if (!Directory.Exists(exportFolder)) {
123 try {
124 // Simplify any ".." relative-ness.
125 exportFolder = new DirectoryInfo(exportFolder).FullName;
126 } catch (Exception) {
127 UnityEngine.Debug.Log("Could not build " + DefinitionName + " because " +
128 "localBuildFolderPath was used and directory " + exportFolder +
129 " could not be created.");
130 }
131 } else {
132 // Simplify any ".." relative-ness.
133 exportFolder = new DirectoryInfo(exportFolder).FullName;
134 }
135 }
136 else if (!TryGetPackageExportFolder(out exportFolder, promptIfNotDefined: true)) {
137 UnityEngine.Debug.LogError("Could not build " + DefinitionName + " because no export folder was chosen.");
138 if (crashWithCodeOnFail) {
139 EditorApplication.Exit((int)ExitCode.NoExportFolderSpecified);
140 }
141 return;
142 }
143
145 string hash;
146 if (tryGetGitCommitHash(out hash)) {
147 exportFolder = Path.Combine(exportFolder, DefinitionName + "_" + hash);
148 } else {
149 UnityEngine.Debug.LogWarning("Failed to get git hash.");
150 }
151 }
152
153 var fullBuildPath = Path.Combine(exportFolder, DefinitionName);
154 string fullExecutablePath = Path.Combine(
155 fullBuildPath,
157 );
158
159 var buildOptions = new BuildPlayerOptions() {
160 scenes = _scenes.Where(s => s != null).
161 Select(s => AssetDatabase.GetAssetPath(s)).
162 ToArray(),
163 options = _options,
164 };
165
166 foreach (var target in _targets) {
167 buildOptions.target = target;
168 buildOptions.locationPathName = fullExecutablePath +
169 getFileSuffix(target);
170
171 if (_useSpecificPlayerSettings) {
172 #if UNITY_2018_3_OR_NEWER
173 var origFullscreenMode = PlayerSettings.fullScreenMode;
174 #endif
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;
180 #endif
181 try {
182 #if UNITY_2018_3_OR_NEWER
183 PlayerSettings.fullScreenMode = _playerSettings.fullScreenMode;
184 #endif
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;
192 #endif
193
194 callPreBuildExtensions(fullBuildPath, crashWithCodeOnFail);
195 BuildPipeline.BuildPlayer(buildOptions);
196 callPostBuildExtensions(fullBuildPath, crashWithCodeOnFail);
197 }
198 finally {
199 #if UNITY_2018_3_OR_NEWER
200 PlayerSettings.fullScreenMode = origFullscreenMode;
201 #endif
202 PlayerSettings.defaultScreenWidth = origDefaultWidth;
203 PlayerSettings.defaultScreenHeight = origDefaultHeight;
204 PlayerSettings.resizableWindow = origResizableWindow;
205 #if !UNITY_2019_1_OR_NEWER
206 PlayerSettings.displayResolutionDialog = origResDialogSetting;
207 #endif
208 }
209 }
210 else {
211 callPreBuildExtensions(fullBuildPath, crashWithCodeOnFail);
212 BuildPipeline.BuildPlayer(buildOptions);
213 callPostBuildExtensions(fullBuildPath, crashWithCodeOnFail);
214 }
215
216 if (_options.HasFlag(BuildOptions.EnableHeadlessMode)) {
217 // The -batchmode flag is the only important part of headless mode
218 // for Windows. The EnableHeadlessMode build option only actually has
219 // an effect on Linux standalone builds.
220 // Here, it's being used to mark the _intention_ of a headless build
221 // for Windows builds.
222 var text = "\"" + _definitionName + ".exe" + "\" -batchmode";
223 var headlessModeBatPath = Path.Combine(Path.Combine(exportFolder,
224 _definitionName), "Run Headless Mode.bat");
225 File.WriteAllText(headlessModeBatPath, text);
226 }
227 }
228
229 if (openFileWindowWhenDone) {
230 Process.Start(exportFolder);
231 }
232 }
233
234 private void callPreBuildExtensions(string exportFolder,
235 bool crashWithCodeOnFail = false) {
236 try {
237 foreach (var preBuildKeyValue in BuildExtensions.preBuildExtensionMethods) {
238 var attribute = preBuildKeyValue.Key;
239 var methodInfo = preBuildKeyValue.Value;
240 //Debug.Log("Calling " + methodInfo.Name);
241 var methodArgs = new object[] { this, exportFolder };
242 methodInfo.Invoke(null, methodArgs);
243 }
244 }
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);
250 }
251 }
252 }
253
254 private void callPostBuildExtensions(string exportFolder,
255 bool crashWithCodeOnFail = false)
256 {
257 try {
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);
263 }
264 }
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);
270 }
271 }
272 }
273
274 private static string getFileSuffix(BuildTarget target) {
275 switch (target) {
276 case BuildTarget.StandaloneWindows:
277 case BuildTarget.StandaloneWindows64:
278 return ".exe";
279 case BuildTarget.Android:
280 return ".apk";
281 case BuildTarget.StandaloneLinux64:
282 return ".x86_64";
283 default:
284 return "";
285 }
286 }
287
288 private static bool tryGetGitCommitHash(out string hash) {
289 try {
290 Process process = new Process();
291
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
300 };
301
302 process.StartInfo = startInfo;
303 process.Start();
304
305 string result = process.StandardOutput.ReadToEnd();
306
307 var match = Regex.Match(result, @"^commit (\w{40})");
308 if (match.Success) {
309 hash = match.Groups[1].Value;
310 return true;
311 } else {
312 hash = "";
313 return false;
314 }
315 } catch (Exception e) {
316 UnityEngine.Debug.LogException(e);
317 hash = "";
318 return false;
319 }
320 }
321
322 [MenuItem("Build/All Apps", priority = 1)]
323 public static void BuildAll() {
324 foreach (var item in EditorResources.FindAllAssetsOfType<BuildDefinition>()) {
325 item.Build();
326 }
327 }
328
329 [MenuItem("Build/All Apps", priority = 1, validate = true)]
330 public static bool ValidateBuildAll() {
331 return EditorResources.FindAllAssetsOfType<BuildDefinition>().Length > 0;
332 }
333#endif
334
335 [System.Serializable]
336 public class BuildExtensionData : SerializableDictionary<string, string> { }
337
338 [Header("Extension Data")]
340
341 }
342
343 [AttributeUsage(validOn: AttributeTargets.Method)]
344 public class PreBuildExtension : Attribute { }
345
346 [AttributeUsage(validOn: AttributeTargets.Method)]
347 public class PostBuildExtension : Attribute { }
348
349 public static class BuildExtensions {
350
351 private static Dictionary<Attribute, MethodInfo>
352 _backingPreBuildExtensionMethods = null;
353 public static Dictionary<Attribute, MethodInfo> preBuildExtensionMethods {
354 get {
355 if (_backingPreBuildExtensionMethods == null) {
356 _backingPreBuildExtensionMethods =
357 new Dictionary<Attribute, MethodInfo>();
358 }
359 return _backingPreBuildExtensionMethods;
360 }
361 }
362
363 private static Dictionary<Attribute, MethodInfo>
364 _backingPostBuildExtensionMethods = null;
365 public static Dictionary<Attribute, MethodInfo> postBuildExtensionMethods {
366 get {
367 if (_backingPostBuildExtensionMethods == null) {
368 _backingPostBuildExtensionMethods =
369 new Dictionary<Attribute, MethodInfo>();
370 }
371 return _backingPostBuildExtensionMethods;
372 }
373 }
374
375 #if UNITY_EDITOR
376 [UnityEditor.InitializeOnLoadMethod]
377 #endif
378 private static void InitializeOnLoad() {
379 // Scan the assembly for PreBuildExtensions and PostBuildExtensions.
380 // var assemblies = AppDomain.CurrentDomain.GetAssemblies();
381 // foreach (var assembly in assemblies) {
382 // foreach (var type in assembly.GetTypes()) {
383 // foreach (var method in type.GetMethods()) {
384 // var preBuildExtensionAttr = method.GetCustomAttribute(
385 // typeof(PreBuildExtension));
386 // var postBuildExtensionAttr = method.GetCustomAttribute(
387 // typeof(PostBuildExtension));
388
389 // if (preBuildExtensionAttr != null) {
390 // preBuildExtensionMethods[preBuildExtensionAttr] = method;
391 // }
392
393 // if (postBuildExtensionAttr != null) {
394 // postBuildExtensionMethods[postBuildExtensionAttr] = method;
395 // }
396 // }
397 // }
398 // }
399 }
400
401 }
402
403}
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19
In order to have this class be serialized, you will always need to create your own non-generic versio...
UnityEngine.Debug Debug
System.Diagnostics.Debug DiagDebug