-
Notifications
You must be signed in to change notification settings - Fork 2.5k
/
BuildPlayerWindowBuildMethods.cs
470 lines (399 loc) · 22.5 KB
/
BuildPlayerWindowBuildMethods.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
using UnityEditor.Modules;
using UnityEditor.Build;
using UnityEngine;
using UnityEditorInternal;
using UnityEditor.Experimental;
using UnityEditor.Scripting.ScriptCompilation;
using System.Collections;
using System.IO;
using System;
using UnityEditor.Build.Reporting;
using UnityEditor.Connect;
using UnityEditor.Utils;
namespace UnityEditor
{
public partial class BuildPlayerWindow : EditorWindow
{
private static Func<BuildPlayerOptions, BuildPlayerOptions> getBuildPlayerOptionsHandler;
private static Action<BuildPlayerOptions> buildPlayerHandler;
private static bool m_Building = false;
internal static Action<BuildReport> buildCompletionHandler;
/// <summary>
/// Exception thrown when an abort or error condition is reached within a build method delegate.
/// </summary>
public class BuildMethodException : System.Exception
{
/// <summary>
/// Constructor for aborting a method without displaying an error.
/// </summary>
public BuildMethodException() : base("") {}
/// <summary>
/// Constructor for aborting on error that will print the log the given message as an error.
/// </summary>
/// <param name="message"></param>
public BuildMethodException(string message) : base(message) {}
}
/// <summary>
/// Register a delegate method to calculate BuildPlayerOptions that are passed to the build process.
/// </summary>
/// <param name="func">Delegate method</param>
public static void RegisterGetBuildPlayerOptionsHandler(Func<BuildPlayerOptions, BuildPlayerOptions> func)
{
// Display a warning if user scripts try to register this delegate multiple times
if (func != null && getBuildPlayerOptionsHandler != null)
Debug.LogWarning("The get build player options handler in BuildPlayerWindow is being reassigned!");
getBuildPlayerOptionsHandler = func;
}
/// <summary>
/// Register a delegate method to execute a player build process.
/// </summary>
/// <param name="func">Delegate method</param>
public static void RegisterBuildPlayerHandler(Action<BuildPlayerOptions> func)
{
// Display a warning if user scripts try to register this delegate multiple times
if (func != null && buildPlayerHandler != null)
Debug.LogWarning("The build player handler in BuildPlayerWindow is being reassigned!");
buildPlayerHandler = func;
}
/// <summary>
/// Method called by the UI when the "Build" or "Build and Run" buttons are pressed.
/// </summary>
/// <param name="defaultBuildOptions"></param>
internal static void CallBuildMethods(bool askForBuildLocation, BuildOptions defaultBuildOptions)
{
if (EditorCompilationInterface.IsCompiling())
{
Debug.LogWarning("Cannot build player while editor is compiling scripts.");
return;
}
// One build at a time!
if (m_Building)
return;
try
{
m_Building = true;
BuildPlayerOptions options = new BuildPlayerOptions();
options.options = defaultBuildOptions;
if (getBuildPlayerOptionsHandler != null)
options = getBuildPlayerOptionsHandler(options);
else
options = DefaultBuildMethods.GetBuildPlayerOptionsInternal(askForBuildLocation, options);
if (buildPlayerHandler != null)
buildPlayerHandler(options);
else
DefaultBuildMethods.BuildPlayer(options);
}
catch (BuildMethodException e)
{
if (!string.IsNullOrEmpty(e.Message))
Debug.LogError(e);
}
finally
{
m_Building = false;
}
}
/// <summary>
/// Default (legacy) implementation of player window build methods.
/// </summary>
public static class DefaultBuildMethods
{
/// <summary>
/// Default implementation of the build player method.
/// </summary>
/// <param name="options"></param>
public static void BuildPlayer(BuildPlayerOptions options)
{
if (!UnityConnect.instance.canBuildWithUPID)
{
if (!EditorUtility.DisplayDialog("Missing Project ID", "Because you are not a member of this project this build will not access Unity services.\nDo you want to continue?", "Yes", "No"))
throw new BuildMethodException();
}
if (!BuildPipeline.IsBuildTargetSupported(options.targetGroup, options.target))
throw new BuildMethodException("Build target is not supported.");
string module = ModuleManager.GetTargetStringFrom(EditorUserBuildSettings.selectedBuildTargetGroup, options.target);
IBuildWindowExtension buildWindowExtension = ModuleManager.GetBuildWindowExtension(module);
if (buildWindowExtension != null && (options.options & BuildOptions.AutoRunPlayer) != 0 && !buildWindowExtension.EnabledBuildAndRunButton())
throw new BuildMethodException();
if (Unsupported.IsBleedingEdgeBuild())
{
var sb = new System.Text.StringBuilder();
sb.AppendLine("This version of Unity is a BleedingEdge build that has not seen any manual testing.");
sb.AppendLine("You should consider this build unstable.");
sb.AppendLine("We strongly recommend that you use a normal version of Unity instead.");
if (EditorUtility.DisplayDialog("BleedingEdge Build", sb.ToString(), "Cancel", "OK"))
throw new BuildMethodException();
}
// See if we need to switch platforms and delay the build. We do this whenever
// we're trying to build for a target different from the active one so as to ensure
// that the compiled script code we have loaded is built for the same platform we
// are building for. As we can't reload while our editor stuff is still executing,
// we need to defer to after the next script reload then.
bool delayToAfterScriptReload = false;
if (EditorUserBuildSettings.activeBuildTarget != options.target ||
EditorUserBuildSettings.activeBuildTargetGroup != options.targetGroup)
{
if (!EditorUserBuildSettings.SwitchActiveBuildTargetAsync(options.targetGroup, options.target))
{
// Switching the build target failed. No point in trying to continue
// with a build.
var errStr = string.Format("Could not switch to build target '{0}', '{1}'.",
BuildPipeline.GetBuildTargetGroupDisplayName(options.targetGroup),
BuildPlatforms.instance.GetBuildTargetDisplayName(options.targetGroup, options.target));
throw new BuildMethodException(errStr);
}
if (EditorApplication.isCompiling)
delayToAfterScriptReload = true;
}
bool locationPathExistedBeforeBuild = System.IO.Directory.Exists(options.locationPathName);
// Trigger build.
// Note: report will be null, if delayToAfterScriptReload = true
var report = BuildPipeline.BuildPlayerInternalNoCheck(options.scenes, options.locationPathName, null, options.targetGroup, options.target, options.options, options.extraScriptingDefines, delayToAfterScriptReload);
if (report != null)
{
var resultStr = String.Format("Build completed with a result of '{0}'", report.summary.result.ToString("g"));
switch (report.summary.result)
{
case Build.Reporting.BuildResult.Unknown:
Debug.LogWarning(resultStr);
break;
case Build.Reporting.BuildResult.Failed:
// On some platforms the user creates the build folder, therefore they own the folder and
// it should not be automatically deleted by the Unity Editor, even if it is empty (case 1073851)
if (options.target != BuildTarget.XboxOne && !locationPathExistedBeforeBuild)
DeleteBuildFolderIfEmpty(report.summary.outputPath);
Debug.LogError(resultStr);
throw new BuildMethodException(report.SummarizeErrors());
default:
Debug.Log(resultStr);
break;
}
buildCompletionHandler?.Invoke(report);
}
}
/// <summary>
/// Default implementation for calculating build options before building the player.
/// </summary>
/// <param name="defaultBuildPlayerOptions"></param>
/// <returns></returns>
public static BuildPlayerOptions GetBuildPlayerOptions(BuildPlayerOptions defaultBuildPlayerOptions)
{
return GetBuildPlayerOptionsInternal(true, defaultBuildPlayerOptions);
}
internal static bool IsInstallInBuildFolderOption()
{
BuildTarget buildTarget = EditorUserBuildSettingsUtils.CalculateSelectedBuildTarget();
BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
return EditorUserBuildSettings.installInBuildFolder &&
PostprocessBuildPlayer.SupportsInstallInBuildFolder(buildTargetGroup, buildTarget) &&
(Unsupported.IsSourceBuild() || IsMetroPlayer(buildTarget));
}
internal static BuildPlayerOptions GetBuildPlayerOptionsInternal(bool askForBuildLocation, BuildPlayerOptions defaultBuildPlayerOptions)
{
var options = defaultBuildPlayerOptions;
bool updateExistingBuild = false;
BuildTarget buildTarget = EditorUserBuildSettingsUtils.CalculateSelectedBuildTarget();
BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
// Pick location for the build
string newLocation = "";
//Check if Lz4 is supported for the current buildtargetgroup and enable it if need be
if (PostprocessBuildPlayer.SupportsLz4Compression(buildTargetGroup, buildTarget))
{
var compression = EditorUserBuildSettings.GetCompressionType(buildTargetGroup);
if (compression < 0)
compression = PostprocessBuildPlayer.GetDefaultCompression(buildTargetGroup, buildTarget);
if (compression == Compression.Lz4)
options.options |= BuildOptions.CompressWithLz4;
else if (compression == Compression.Lz4HC)
options.options |= BuildOptions.CompressWithLz4HC;
}
bool developmentBuild = EditorUserBuildSettings.development;
if (developmentBuild)
options.options |= BuildOptions.Development;
if (EditorUserBuildSettings.allowDebugging && developmentBuild)
options.options |= BuildOptions.AllowDebugging;
if (EditorUserBuildSettings.symlinkLibraries)
options.options |= BuildOptions.SymlinkLibraries;
if (EditorUserBuildSettings.enableHeadlessMode)
options.options |= BuildOptions.EnableHeadlessMode;
if (EditorUserBuildSettings.connectProfiler && (developmentBuild || buildTarget == BuildTarget.WSAPlayer))
options.options |= BuildOptions.ConnectWithProfiler;
if (EditorUserBuildSettings.buildWithDeepProfilingSupport && developmentBuild)
options.options |= BuildOptions.EnableDeepProfilingSupport;
if (EditorUserBuildSettings.buildScriptsOnly)
options.options |= BuildOptions.BuildScriptsOnly;
if (IsInstallInBuildFolderOption())
{
options.options |= BuildOptions.InstallInBuildFolder;
}
else
{
if (askForBuildLocation && !PickBuildLocation(buildTargetGroup, buildTarget, options.options, out updateExistingBuild))
throw new BuildMethodException();
newLocation = EditorUserBuildSettings.GetBuildLocation(buildTarget);
if (newLocation.Length == 0)
{
throw new BuildMethodException("Build location for buildTarget " + buildTarget + "is not valid.");
}
if (!askForBuildLocation)
{
switch (UnityEditorInternal.InternalEditorUtility.BuildCanBeAppended(buildTarget, newLocation))
{
case CanAppendBuild.Unsupported:
break;
case CanAppendBuild.Yes:
updateExistingBuild = true;
break;
case CanAppendBuild.No:
if (!PickBuildLocation(buildTargetGroup, buildTarget, options.options, out updateExistingBuild))
throw new BuildMethodException();
newLocation = EditorUserBuildSettings.GetBuildLocation(buildTarget);
if (!BuildLocationIsValid(newLocation))
throw new BuildMethodException("Build location for buildTarget " + buildTarget + "is not valid.");
break;
}
}
}
if (updateExistingBuild)
options.options |= BuildOptions.AcceptExternalModificationsToPlayer;
options.target = buildTarget;
options.targetGroup = buildTargetGroup;
options.locationPathName = EditorUserBuildSettings.GetBuildLocation(buildTarget);
options.assetBundleManifestPath = null;
// Build a list of scenes that are enabled
ArrayList scenesList = new ArrayList();
EditorBuildSettingsScene[] editorScenes = EditorBuildSettings.scenes;
foreach (EditorBuildSettingsScene scene in editorScenes)
{
if (scene.enabled)
scenesList.Add(scene.path);
}
options.scenes = scenesList.ToArray(typeof(string)) as string[];
return options;
}
private static bool PickBuildLocation(BuildTargetGroup targetGroup, BuildTarget target, BuildOptions options, out bool updateExistingBuild)
{
updateExistingBuild = false;
var previousPath = EditorUserBuildSettings.GetBuildLocation(target);
string defaultFolder;
string defaultName;
if (previousPath == String.Empty)
{
defaultFolder = FileUtil.DeleteLastPathNameComponent(Application.dataPath);
defaultName = "";
}
else
{
defaultFolder = FileUtil.DeleteLastPathNameComponent(previousPath);
defaultName = FileUtil.GetLastPathNameComponent(previousPath);
}
string extension = PostprocessBuildPlayer.GetExtensionForBuildTarget(targetGroup, target, options);
// Invalidate default name, if extension mismatches the default file (for ex., when switching between folder type export to file type export, see Android)
if (extension != Path.GetExtension(defaultName).Replace(".", ""))
defaultName = string.Empty;
// Hack: For Windows Standalone, we want the BuildPanel to choose a folder,
// but we don't want BuildPlayer to take a folder path because historically it took an .exe path
// and we would be breaking tons of projects!
bool isWindowsStandalone = target == BuildTarget.StandaloneWindows || target == BuildTarget.StandaloneWindows64;
string realExtension = extension;
if (isWindowsStandalone)
{
extension = string.Empty;
// Remove the filename.exe part from the path
if (!string.IsNullOrEmpty(defaultName))
defaultName = Path.GetDirectoryName(defaultName);
}
string title = "Build " + BuildPlatforms.instance.GetBuildTargetDisplayName(targetGroup, target);
string path = EditorUtility.SaveBuildPanel(target, title, defaultFolder, defaultName, extension, out updateExistingBuild);
if (path == string.Empty)
return false;
if (isWindowsStandalone)
{
extension = realExtension;
path = Path.Combine(path, Paths.MakeValidFileName(PlayerSettings.productName) + '.' + extension);
}
if (!IsBuildPathValid(path))
return false;
// Enforce extension if needed
if (extension != string.Empty && FileUtil.GetPathExtension(path).ToLower() != extension)
path += '.' + extension;
// A path may not be empty initially, but it could contain, e.g., a drive letter (as in Windows),
// so even appending an extension will work fine, but in reality the name will be, for example,
// G:/
//Debug.Log(path);
string currentlyChosenName = FileUtil.GetLastPathNameComponent(path);
if (currentlyChosenName == string.Empty)
return false; // No nameless projects, please
// We don't want to re-create a directory that already exists, this may
// result in access-denials that will make users unhappy.
string check_dir = extension != string.Empty ? FileUtil.DeleteLastPathNameComponent(path) : path;
if (!Directory.Exists(check_dir))
Directory.CreateDirectory(check_dir);
// On OSX we've got replace/update dialog, for other platforms warn about deleting
// files in target folder.
if ((target == BuildTarget.iOS) && (Application.platform != RuntimePlatform.OSXEditor))
if (!FolderIsEmpty(path) && !UserWantsToDeleteFiles(path))
return false;
EditorUserBuildSettings.SetBuildLocation(target, path);
return true;
}
private static string NormalizePath(string path)
{
var fullPath = path;
if (fullPath.Length > 1 && fullPath[fullPath.Length - 1] == '.' && fullPath[fullPath.Length - 2] != '.')
fullPath = fullPath.Remove(fullPath.Length - 1);
if (fullPath.EndsWith("/") || fullPath.EndsWith("\\"))
fullPath = fullPath.Remove(fullPath.Length - 1);
fullPath = string.IsNullOrEmpty(fullPath) ? string.Empty : Path.GetFullPath(fullPath);
fullPath = fullPath.ToLower();
if (Path.DirectorySeparatorChar == '/')
return fullPath;
return fullPath.Replace(Path.DirectorySeparatorChar, '/');
}
internal static bool IsBuildPathValid(string path)
{
var cleanedPath = NormalizePath(path);
if (cleanedPath.Equals(string.Empty) &&
IsInstallInBuildFolderOption())
return true;
var basePath = NormalizePath(Application.dataPath + "/../");
var assetsPath = NormalizePath(basePath + "/Assets");
var settingsPath = NormalizePath(basePath + "/ProjectSettings");
var tempPath = NormalizePath(basePath + "/Temp");
var libraryPath = NormalizePath(basePath + "/Library");
var userSettingsPath = NormalizePath(basePath + "/UserSettings");
if (basePath.Contains(cleanedPath) || cleanedPath == assetsPath || cleanedPath == settingsPath || cleanedPath == tempPath || cleanedPath == libraryPath || cleanedPath == userSettingsPath)
{
Debug.LogError("Invalid build path: " + cleanedPath);
return false;
}
return true;
}
private static void DeleteBuildFolderIfEmpty(string path)
{
if (Directory.Exists(path) && FolderIsEmpty(path))
Directory.Delete(path);
}
private static bool FolderIsEmpty(string path)
{
if (!Directory.Exists(path))
return true;
return (Directory.GetDirectories(path).Length == 0)
&& (Directory.GetFiles(path).Length == 0);
}
private static bool UserWantsToDeleteFiles(string path)
{
string text =
"WARNING: all files and folders located in target folder: '" + path + "' will be deleted by build process.";
return EditorUtility.DisplayDialog("Deleting existing files", text, "OK", "Cancel");
}
private static bool IsMetroPlayer(BuildTarget target)
{
return target == BuildTarget.WSAPlayer;
}
}
}
}