AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
BuildSLNUtilities.cs
Go to the documentation of this file.
1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT License. See LICENSE in the project root for license information.
3 
4 using System;
5 using System.Collections.Generic;
6 using System.Globalization;
7 using System.IO;
8 using System.Linq;
9 using System.Xml;
10 using UnityEditor;
11 using UnityEngine;
12 
13 #if UNITY_2018_1_OR_NEWER
14 using UnityEditor.Build.Reporting;
15 #endif
16 
17 namespace HoloToolkit.Unity
18 {
22  public static class BuildSLNUtilities
23  {
28  public delegate void BuildInfoConfigurationMethod(ref BuildInfo toConfigure);
29 
34  public static event BuildInfoConfigurationMethod OverrideBuildDefaults;
35 
41  public static void RaiseOverrideBuildDefaults(ref BuildInfo toConfigure)
42  {
43  if (OverrideBuildDefaults != null)
44  {
45  OverrideBuildDefaults(ref toConfigure);
46  }
47  }
48 
49  // Build configurations. Exactly one of these should be defined for any given build.
50  public const string BuildSymbolDebug = "DEBUG";
51  public const string BuildSymbolRelease = "RELEASE";
52  public const string BuildSymbolMaster = "MASTER";
53 
57  public static event Action<BuildInfo> BuildStarted;
58 
59 #if UNITY_2018_1_OR_NEWER
60  public static event Action<BuildInfo, BuildReport> BuildCompleted;
64 #else
65  public static event Action<BuildInfo, string> BuildCompleted;
69 #endif
70 
71  public static void PerformBuild(BuildInfo buildInfo)
72  {
73  BuildTargetGroup buildTargetGroup = GetGroup(buildInfo.BuildTarget);
74  string oldBuildSymbols = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup);
75  if (!string.IsNullOrEmpty(oldBuildSymbols))
76  {
77  if (buildInfo.HasConfigurationSymbol())
78  {
79  buildInfo.AppendSymbols(BuildInfo.RemoveConfigurationSymbols(oldBuildSymbols));
80  }
81  else
82  {
83  buildInfo.AppendSymbols(oldBuildSymbols.Split(';'));
84  }
85  }
86 
87  if ((buildInfo.BuildOptions & BuildOptions.Development) == BuildOptions.Development)
88  {
89  if (!buildInfo.HasConfigurationSymbol())
90  {
91  buildInfo.AppendSymbols(BuildSymbolDebug);
92  }
93  }
94 
95  if (buildInfo.HasAnySymbols(BuildSymbolDebug))
96  {
97  buildInfo.BuildOptions |= BuildOptions.Development | BuildOptions.AllowDebugging;
98  }
99 
100  if (buildInfo.HasAnySymbols(BuildSymbolRelease))
101  {
102  //Unity automatically adds the DEBUG symbol if the BuildOptions.Development flag is
103  //specified. In order to have debug symbols and the RELEASE symbols we have to
104  //inject the symbol Unity relies on to enable the /debug+ flag of csc.exe which is "DEVELOPMENT_BUILD"
105  buildInfo.AppendSymbols("DEVELOPMENT_BUILD");
106  }
107 
108  BuildTarget oldBuildTarget = EditorUserBuildSettings.activeBuildTarget;
109  BuildTargetGroup oldBuildTargetGroup = GetGroup(oldBuildTarget);
110 
111  EditorUserBuildSettings.SwitchActiveBuildTarget(buildTargetGroup, buildInfo.BuildTarget);
112 
113  WSAUWPBuildType? oldWSAUWPBuildType = EditorUserBuildSettings.wsaUWPBuildType;
114 
115  if (buildInfo.WSAUWPBuildType.HasValue)
116  {
117  EditorUserBuildSettings.wsaUWPBuildType = buildInfo.WSAUWPBuildType.Value;
118  }
119 
120  var oldWSAGenerateReferenceProjects = EditorUserBuildSettings.wsaGenerateReferenceProjects;
121 
122  if (buildInfo.WSAGenerateReferenceProjects.HasValue)
123  {
124  EditorUserBuildSettings.wsaGenerateReferenceProjects = buildInfo.WSAGenerateReferenceProjects.Value;
125  }
126 
127  var oldColorSpace = PlayerSettings.colorSpace;
128 
129  if (buildInfo.ColorSpace.HasValue)
130  {
131  PlayerSettings.colorSpace = buildInfo.ColorSpace.Value;
132  }
133 
134  if (buildInfo.BuildSymbols != null)
135  {
136  PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, buildInfo.BuildSymbols);
137  }
138 
139  // For the WSA player, Unity builds into a target directory.
140  // For other players, the OutputPath parameter indicates the
141  // path to the target executable to build.
142  if (buildInfo.BuildTarget == BuildTarget.WSAPlayer)
143  {
144  Directory.CreateDirectory(buildInfo.OutputDirectory);
145  }
146 
147  OnPreProcessBuild(buildInfo);
148 
149  EditorUtility.DisplayProgressBar("Build Pipeline", "Gathering Build data...", 0.25f);
150 
151 #if UNITY_2018_1_OR_NEWER
152  BuildReport buildReport = default(BuildReport);
153 #else
154  string buildReport = "ERROR";
155 #endif
156  try
157  {
158  buildReport = BuildPipeline.BuildPlayer(
159  buildInfo.Scenes.ToArray(),
160  buildInfo.OutputDirectory,
161  buildInfo.BuildTarget,
162  buildInfo.BuildOptions);
163 
164 #if UNITY_2018_1_OR_NEWER
165  if (buildReport.summary.result != BuildResult.Succeeded)
166  {
167  throw new Exception(string.Format("Build Result: {0}", buildReport.summary.result.ToString()));
168  }
169 #else
170  if (buildReport.StartsWith("Error"))
171  {
172  throw new Exception(buildReport);
173  }
174 #endif
175  }
176  finally
177  {
178  OnPostProcessBuild(buildInfo, buildReport);
179 
180  if (buildInfo.BuildTarget == BuildTarget.WSAPlayer && EditorUserBuildSettings.wsaGenerateReferenceProjects)
181  {
183  }
184 
185  PlayerSettings.colorSpace = oldColorSpace;
186  PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, oldBuildSymbols);
187 
188  EditorUserBuildSettings.wsaUWPBuildType = oldWSAUWPBuildType.Value;
189 
190  EditorUserBuildSettings.wsaGenerateReferenceProjects = oldWSAGenerateReferenceProjects;
191  EditorUserBuildSettings.SwitchActiveBuildTarget(oldBuildTargetGroup, oldBuildTarget);
192  }
193  }
194 
195  public static void ParseBuildCommandLine(ref BuildInfo buildInfo)
196  {
197  string[] arguments = Environment.GetCommandLineArgs();
198 
199  buildInfo.IsCommandLine = true;
200 
201  for (int i = 0; i < arguments.Length; ++i)
202  {
203  // Can't use -buildTarget which is something Unity already takes as an argument for something.
204  if (string.Equals(arguments[i], "-duskBuildTarget", StringComparison.InvariantCultureIgnoreCase))
205  {
206  buildInfo.BuildTarget = (BuildTarget)Enum.Parse(typeof(BuildTarget), arguments[++i]);
207  }
208  else if (string.Equals(arguments[i], "-wsaSDK", StringComparison.InvariantCultureIgnoreCase))
209  {
210  string wsaSdkArg = arguments[++i];
211 
212  buildInfo.WSASdk = (WSASDK)Enum.Parse(typeof(WSASDK), wsaSdkArg);
213  }
214  else if (string.Equals(arguments[i], "-wsaUwpSdk", StringComparison.InvariantCultureIgnoreCase))
215  {
216  buildInfo.WSAUwpSdk = arguments[++i];
217  }
218  else if (string.Equals(arguments[i], "-wsaUWPBuildType", StringComparison.InvariantCultureIgnoreCase))
219  {
220  buildInfo.WSAUWPBuildType = (WSAUWPBuildType)Enum.Parse(typeof(WSAUWPBuildType), arguments[++i]);
221  }
222  else if (string.Equals(arguments[i], "-wsaGenerateReferenceProjects", StringComparison.InvariantCultureIgnoreCase))
223  {
224  buildInfo.WSAGenerateReferenceProjects = bool.Parse(arguments[++i]);
225  }
226  else if (string.Equals(arguments[i], "-buildOutput", StringComparison.InvariantCultureIgnoreCase))
227  {
228  buildInfo.OutputDirectory = arguments[++i];
229  }
230  else if (string.Equals(arguments[i], "-buildDesc", StringComparison.InvariantCultureIgnoreCase))
231  {
232  ParseBuildDescriptionFile(arguments[++i], ref buildInfo);
233  }
234  else if (string.Equals(arguments[i], "-unityBuildSymbols", StringComparison.InvariantCultureIgnoreCase))
235  {
236  string newBuildSymbols = arguments[++i];
237  buildInfo.AppendSymbols(newBuildSymbols.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries));
238  }
239  }
240  }
241 
242  public static void PerformBuild_CommandLine()
243  {
244  var buildInfo = new BuildInfo
245  {
246  // Use scenes from the editor build settings.
247  Scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(scene => scene.path),
248 
249  // Configure a post build action to throw appropriate error code.
250  PostBuildAction = (innerBuildInfo, buildReport) =>
251  {
252 #if UNITY_2018_1_OR_NEWER
253  if (buildReport.summary.result != BuildResult.Succeeded)
254 #else
255  if (!string.IsNullOrEmpty(buildReport))
256 #endif
257  {
258  EditorApplication.Exit(1);
259  }
260  }
261  };
262 
263  RaiseOverrideBuildDefaults(ref buildInfo);
264  ParseBuildCommandLine(ref buildInfo);
265  PerformBuild(buildInfo);
266  }
267 
268  public static void ParseBuildDescriptionFile(string filename, ref BuildInfo buildInfo)
269  {
270  Debug.Log(string.Format(CultureInfo.InvariantCulture, "Build: Using \"{0}\" as build description", filename));
271 
272  // Parse the XML file
273  var reader = new XmlTextReader(filename);
274 
275  while (reader.Read())
276  {
277  switch (reader.NodeType)
278  {
279  case XmlNodeType.Element:
280  if (string.Equals(reader.Name, "SceneList", StringComparison.InvariantCultureIgnoreCase))
281  {
282  // Set the scenes we want to build
283  buildInfo.Scenes = ReadSceneList(reader);
284  }
285  else if (string.Equals(reader.Name, "CopyList", StringComparison.InvariantCultureIgnoreCase))
286  {
287  // Set the directories we want to copy
288  buildInfo.CopyDirectories = ReadCopyList(reader);
289  }
290  break;
291  }
292  }
293  }
294 
295  private static BuildTargetGroup GetGroup(BuildTarget buildTarget)
296  {
297  switch (buildTarget)
298  {
299  case BuildTarget.WSAPlayer:
300  return BuildTargetGroup.WSA;
301  case BuildTarget.StandaloneWindows:
302  case BuildTarget.StandaloneWindows64:
303  return BuildTargetGroup.Standalone;
304  default:
305  return BuildTargetGroup.Unknown;
306  }
307  }
308 
309  private static IEnumerable<string> ReadSceneList(XmlTextReader reader)
310  {
311  var result = new List<string>();
312  while (reader.Read())
313  {
314  switch (reader.NodeType)
315  {
316  case XmlNodeType.Element:
317  if (string.Equals(reader.Name, "Scene", StringComparison.InvariantCultureIgnoreCase))
318  {
319  while (reader.MoveToNextAttribute())
320  {
321  if (string.Equals(reader.Name, "Name", StringComparison.InvariantCultureIgnoreCase))
322  {
323  result.Add(reader.Value);
324  Debug.Log(string.Format(CultureInfo.InvariantCulture, "Build: Adding scene \"{0}\"", reader.Value));
325  }
326  }
327  }
328  break;
329 
330  case XmlNodeType.EndElement:
331  if (string.Equals(reader.Name, "SceneList", StringComparison.InvariantCultureIgnoreCase))
332  {
333  return result;
334  }
335  break;
336  }
337  }
338 
339  return result;
340  }
341 
342  private static IEnumerable<CopyDirectoryInfo> ReadCopyList(XmlTextReader reader)
343  {
344  var result = new List<CopyDirectoryInfo>();
345  while (reader.Read())
346  {
347  switch (reader.NodeType)
348  {
349  case XmlNodeType.Element:
350  if (string.Equals(reader.Name, "Copy", StringComparison.InvariantCultureIgnoreCase))
351  {
352  string source = null;
353  string dest = null;
354  string filter = null;
355  bool recursive = false;
356 
357  while (reader.MoveToNextAttribute())
358  {
359  if (string.Equals(reader.Name, "Source", StringComparison.InvariantCultureIgnoreCase))
360  {
361  source = reader.Value;
362  }
363  else if (string.Equals(reader.Name, "Destination", StringComparison.InvariantCultureIgnoreCase))
364  {
365  dest = reader.Value;
366  }
367  else if (string.Equals(reader.Name, "Recursive", StringComparison.InvariantCultureIgnoreCase))
368  {
369  recursive = Convert.ToBoolean(reader.Value);
370  }
371  else if (string.Equals(reader.Name, "Filter", StringComparison.InvariantCultureIgnoreCase))
372  {
373  filter = reader.Value;
374  }
375  }
376 
377  if (source != null)
378  {
379  // Either the file specifies the Destination as well, or else CopyDirectory will use Source for Destination
380  var info = new CopyDirectoryInfo { Source = source };
381 
382  if (dest != null)
383  {
384  info.Destination = dest;
385  }
386 
387  if (filter != null)
388  {
389  info.Filter = filter;
390  }
391 
392  info.Recursive = recursive;
393 
394  Debug.Log(string.Format(CultureInfo.InvariantCulture, @"Build: Adding {0}copy ""{1}\{2}"" => ""{3}""", info.Recursive ? "Recursive " : "", info.Source, info.Filter, info.Destination ?? info.Source));
395 
396  result.Add(info);
397  }
398  }
399  break;
400 
401  case XmlNodeType.EndElement:
402  if (string.Equals(reader.Name, "CopyList", StringComparison.InvariantCultureIgnoreCase))
403  return result;
404  break;
405  }
406  }
407 
408  return result;
409  }
410 
411  public static void CopyDirectory(string sourceDirectoryPath, string destinationDirectoryPath, CopyDirectoryInfo directoryInfo)
412  {
413  sourceDirectoryPath = Path.Combine(sourceDirectoryPath, directoryInfo.Source);
414  destinationDirectoryPath = Path.Combine(destinationDirectoryPath, directoryInfo.Destination ?? directoryInfo.Source);
415 
416  Debug.Log(string.Format(CultureInfo.InvariantCulture, @"{0} ""{1}\{2}"" to ""{3}""", directoryInfo.Recursive ? "Recursively copying" : "Copying", sourceDirectoryPath, directoryInfo.Filter, destinationDirectoryPath));
417 
418  foreach (string sourceFilePath in Directory.GetFiles(sourceDirectoryPath, directoryInfo.Filter, directoryInfo.Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
419  {
420  string destinationFilePath = sourceFilePath.Replace(sourceDirectoryPath, destinationDirectoryPath);
421  try
422  {
423  Directory.CreateDirectory(Path.GetDirectoryName(destinationFilePath));
424  if (File.Exists(destinationFilePath))
425  {
426  File.SetAttributes(destinationFilePath, FileAttributes.Normal);
427  }
428  File.Copy(sourceFilePath, destinationFilePath, true);
429  File.SetAttributes(destinationFilePath, FileAttributes.Normal);
430  }
431  catch (Exception exception)
432  {
433  Debug.LogError(string.Format(CultureInfo.InvariantCulture, "Failed to copy \"{0}\" to \"{1}\" with \"{2}\"", sourceFilePath, destinationFilePath, exception));
434  }
435  }
436  }
437 
438  private static void OnPreProcessBuild(BuildInfo buildInfo)
439  {
440  // Raise the global event for listeners
441  BuildStarted.RaiseEvent(buildInfo);
442 
443  // Call the pre-build action, if any
444  if (buildInfo.PreBuildAction != null)
445  {
446  buildInfo.PreBuildAction(buildInfo);
447  }
448  }
449 
450 
451 #if UNITY_2018_1_OR_NEWER
452  private static void OnPostProcessBuild(BuildInfo buildInfo, BuildReport buildReport)
453  {
454  if (buildReport.summary.result == BuildResult.Succeeded)
455 #else
456  private static void OnPostProcessBuild(BuildInfo buildInfo, string buildReport)
457  {
458  if (string.IsNullOrEmpty(buildReport))
459 #endif
460  {
461  string outputProjectDirectoryPath = Path.Combine(GetProjectPath(), buildInfo.OutputDirectory);
462  if (buildInfo.CopyDirectories != null)
463  {
464  string inputProjectDirectoryPath = GetProjectPath();
465  foreach (var directory in buildInfo.CopyDirectories)
466  {
467  CopyDirectory(inputProjectDirectoryPath, outputProjectDirectoryPath, directory);
468  }
469  }
470  }
471 
472  // Raise the global event for listeners
473  BuildCompleted.RaiseEvent(buildInfo, buildReport);
474 
475  // Call the post-build action, if any
476  if (buildInfo.PostBuildAction != null)
477  {
478  buildInfo.PostBuildAction(buildInfo, buildReport);
479  }
480  }
481 
482  public static string GetProjectPath()
483  {
484  return Path.GetDirectoryName(Path.GetFullPath(Application.dataPath));
485  }
486  }
487 }
static void PerformBuild(BuildInfo buildInfo)
static void CopyDirectory(string sourceDirectoryPath, string destinationDirectoryPath, CopyDirectoryInfo directoryInfo)
This class is designed to post process the UWP Assembly-CSharp projects to ensure that the defaults a...
Action< BuildInfo, string > PostBuildAction
Definition: BuildInfo.cs:25
static void Execute(string buildRootPath)
Executes the Post Processes on the C# Projects generated as part of the UWP build.
WSAUWPBuildType WSAUWPBuildType
Definition: BuildInfo.cs:36
Class containing various utility methods to build a WSA solution from a Unity project.
BuildOptions BuildOptions
Definition: BuildInfo.cs:28
IEnumerable< string > Scenes
Definition: BuildInfo.cs:16
static void RaiseOverrideBuildDefaults(ref BuildInfo toConfigure)
Call this method to give other code an opportunity to override BuildInfo defaults.
static BuildInfoConfigurationMethod OverrideBuildDefaults
Add a handler to this event to override BuildInfo defaults before a build.
bool HasAnySymbols(params string[] symbols)
Definition: BuildInfo.cs:74
IEnumerable< CopyDirectoryInfo > CopyDirectories
Definition: BuildInfo.cs:18
void AppendSymbols(params string[] symbol)
Definition: BuildInfo.cs:51
Action< BuildInfo > PreBuildAction
Definition: BuildInfo.cs:20
static IEnumerable< string > RemoveConfigurationSymbols(string symbols)
Definition: BuildInfo.cs:87
static void ParseBuildDescriptionFile(string filename, ref BuildInfo buildInfo)
static void ParseBuildCommandLine(ref BuildInfo buildInfo)
static Action< BuildInfo > BuildStarted
Event triggered when a build starts.