/
BuildConfiguration.cs
355 lines (318 loc) · 15.6 KB
/
BuildConfiguration.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
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace UnExt.Editor {
/// <summary>
/// Class to create and build configurations, and execute the
/// build process from Unity scripts.
/// Note: using this functionality requires Unity Pro (as of Unity 4.3)
/// Use BuildConfiguration.Create to get a clean configuration,
/// set, add and remove methods to customize it
/// and Build to create the desired executable.
/// Each call to methods which modify settings create a new configuration
/// and return it, so previous configurations are not overwritten.
/// Check the example to check how to take advantadge of it.
/// </summary>
/// <example>
/// // Create a configuration with common parameters for builds.
/// BuildConfiguration baseConfig = BuildConfiguration.Create()
/// .SetExecName( "CoolName" )
/// .AddScene( "OutlineTest" )
/// .AddBuildOption( BuildOptions.ShowBuiltPlayer )
/// .AddBuildOption( BuildOptions.AutoRunPlayer )
/// .AddBuildOption( BuildOptions.Development )
/// .RemoveBuildOption( BuildOptions.AutoRunPlayer )
/// .AddFileMapping( "Sprites/Ship.png", "Ship.png" )
/// .AddDirMapping( "Sprites", "Sprites" );
///
/// // Create a basic windows build
/// baseConfig.SetTargetDir( "../../Builds/Win" )
/// .Build( BuildTarget.StandaloneWindows );
///
/// // Create a Linux build with customized settings.
/// // Each call to base config methods (except build)
/// // generates a new configuration, so baseConfig remains unchanged.
/// baseConfig.SetTargetDir( "../../Builds/linux_x86" )
/// .SetExecName( baseConfig.ExecName.ToLower() )
/// .RemoveBuildOption( BuildOptions.AutoRunPlayer )
/// .Build( BuildTarget.StandaloneLinux );
///
/// // Retains the original exec name and auto run setting.
/// baseConfig.SetTargetDir( "../../Builds/OSXUniversal" )
/// .Build( BuildTarget.StandaloneOSXUniversal );
/// </example>
public class BuildConfiguration {
string targetDir;
/// <summary>
/// Get the directory where the build will be generated.
/// </summary>
public string TargetDir { get { return this.targetDir; } }
string execName;
/// <summary>
/// Get the name of the executable to generate, sans extensions.
/// </summary>
public string ExecName { get { return this.execName; } }
List<string> scenes;
BuildOptions options;
/// <summary>
/// Get the currently configured build options.
/// </summary>
public BuildOptions Options { get { return this.options; } }
Dictionary<string, string> dirMappings;
Dictionary<string, string> fileMappings;
private BuildConfiguration() {
this.scenes = new List<string>();
this.dirMappings = new Dictionary<string, string>();
this.fileMappings = new Dictionary<string, string>();
this.options = BuildOptions.None;
}
/// <summary>
/// Copy constructor.
/// </summary>
/// <param name="other">Configuration to replicate.</param>
private BuildConfiguration(BuildConfiguration other) {
this.scenes = new List<string>( other.scenes );
this.dirMappings = new Dictionary<string, string>();
foreach (var mapping in other.dirMappings) {
this.dirMappings[mapping.Key] = mapping.Value;
}
this.fileMappings = new Dictionary<string, string>( other.fileMappings );
foreach (var mapping in other.fileMappings) {
this.fileMappings[mapping.Key] = mapping.Value;
}
this.options = other.Options;
this.targetDir = other.targetDir;
this.execName = other.execName;
}
/// <summary>
/// Create a clean configuration.
/// </summary>
/// <returns>A new build configuration.</returns>
public static BuildConfiguration Create() {
return new BuildConfiguration();
}
/// <summary>
/// Set the name of the executable file to generate.
/// Do not include file extensions (i.e. ".exe").
/// </summary>
/// <param name="execName">Desired name for the executable.</param>
/// <returns>A copy of this configuration, with the executable name.</returns>
public BuildConfiguration SetExecName(string execName) {
var clone = new BuildConfiguration( this );
clone.execName = execName;
return clone;
}
/// <summary>
/// Add a scene to the executable. At least one is needed.
/// </summary>
/// <param name="scenename">The path of the scene to add.</param>
/// <returns>A copy of this configuration, with the scene added.</returns>
public BuildConfiguration AddScene(string scenename) {
var clone = new BuildConfiguration( this );
clone.scenes.Add( scenename );
return clone;
}
/// <summary>
/// Set the directory where the builds should be placed.
/// </summary>
/// <param name="pathname">Path where builds are placed.</param>
/// <returns>A copy of this configuration, with the target dir.</returns>
public BuildConfiguration SetTargetDir(string pathname) {
var clone = new BuildConfiguration( this );
clone.targetDir = pathname;
return clone;
}
/// <summary>
/// Add Unity build options. Can do it one by one,
/// or add a mask of several BuildOptions.
/// </summary>
/// <param name="_options">Option or options to use when creating the build.</param>
/// <returns>A copy of this configuration, with the new options.</returns>
public BuildConfiguration AddBuildOption(BuildOptions _options) {
var clone = new BuildConfiguration( this );
clone.options |= _options;
return clone;
}
/// <summary>
/// Remove a previously set build option.
/// </summary>
/// <param name="_options">Option or options to remove.</param>
/// <returns>A copy of this configuration, without the given options.</returns>
public BuildConfiguration RemoveBuildOption(BuildOptions _options) {
var clone = new BuildConfiguration( this );
clone.options &= ~_options;
return clone;
}
/// <summary>
/// Configure a directory to copy recursively to the target data directory
/// after the build is completed.
/// A source directory can only appear once.
/// </summary>
/// <param name="projectDirPath">Source directory to copy.</param>
/// <param name="buildDirPath">Target directory to copy the source to.
/// The names or relative paths may not coincide.</param>
/// <returns>A copy of this configuration, with a new directory mapping.</returns>
public BuildConfiguration AddDirMapping(string projectDirPath, string buildDirPath) {
var clone = new BuildConfiguration( this );
clone.dirMappings.Add( projectDirPath, buildDirPath );
return clone;
}
/// <summary>
/// Remove a directory mapping.
/// </summary>
/// <param name="projectDirPath">Project path to not copy.</param>
/// <returns>A copy of this configuration, without the given directory mapping.</returns>
public BuildConfiguration RemoveDirMapping(string projectDirPath) {
var clone = new BuildConfiguration( this );
clone.dirMappings.Remove( projectDirPath );
return clone;
}
/// <summary>
/// Configure a file which shall be copied to the build data directory.
/// </summary>
/// <param name="projectFilePath">Path to the file to copy.</param>
/// <param name="buildFilePath">Path where the file shall be copied.
/// Names or relative paths may not match.</param>
/// <returns>A copy of this configuration, with a new file mapping.</returns>
public BuildConfiguration AddFileMapping(string projectFilePath, string buildFilePath) {
var clone = new BuildConfiguration( this );
clone.fileMappings.Add( projectFilePath, buildFilePath );
return clone;
}
/// <summary>
/// Remove a file from the set of files to copy.
/// </summary>
/// <param name="projectFilePath">Path to the file to not copy.</param>
/// <returns>A copy of this configuration, without the file mapping.</returns>
public BuildConfiguration RemoveFile(string projectFilePath) {
var clone = new BuildConfiguration( this );
clone.fileMappings.Remove( projectFilePath );
return clone;
}
/// <summary>
/// Generate the configured build for the specified target.
/// </summary>
/// <param name="target">Target platform of the build.</param>
public void Build(BuildTarget target) {
if (!Directory.Exists( this.targetDir )) {
Directory.CreateDirectory( this.targetDir );
}
string targetDataDir = null;
string targetExe = null;
this.GetTargetPaths( target, out targetDataDir, out targetExe );
//Build
string buildMessage = BuildPipeline.BuildPlayer( this.scenes.ToArray(),
targetExe,
target,
this.options );
bool buildError = !string.IsNullOrEmpty( buildMessage );
if (buildError) {
Debug.LogError( "Error building " + targetExe + " for " + target + ": " + buildMessage );
return;
} else {
Debug.Log( new DirectoryInfo( this.targetDir ).FullName + " sucessfully built" );
}
if (targetDataDir != null) {
foreach (var mapping in this.dirMappings) {
Directory.CreateDirectory( targetDataDir + "/" + mapping.Value );
CopyFilesRecursively(
new DirectoryInfo( Application.dataPath + "/" + mapping.Key ),
new DirectoryInfo( targetDataDir + "/" + mapping.Value ) );
}
foreach (var mapping in this.fileMappings) {
FileInfo sourceFile = new FileInfo( Application.dataPath + "/" + mapping.Key );
FileInfo targetFile = new FileInfo( targetDataDir + "/" + mapping.Value );
if (!sourceFile.Exists) {
throw new System.IO.FileNotFoundException( "Unable to copy file.", sourceFile.FullName );
}
if (!targetFile.Directory.Exists) {
BuildConfiguration.CreateHierarchy( targetFile.Directory );
}
sourceFile.CopyTo( targetFile.FullName, true );
}
}
}
/// <summary>
/// Create directory hierarchy.
/// </summary>
/// <param name="dir">Highest level directory to create.</param>
private static void CreateHierarchy(DirectoryInfo dir) {
if (!dir.Parent.Exists) {
CreateHierarchy( dir.Parent );
}
dir.Create();
}
/// <summary>
/// Copy files and directorys recursively from one directory to another.
///
/// </summary>
/// <param name="source">Source directory.</param>
/// <param name="target">Target directory.</param>
private static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target) {
foreach (DirectoryInfo dir in source.GetDirectories()) {
string targetDirName = target.FullName + "/" + dir.Name;
DirectoryInfo targetDir = Directory.Exists( targetDirName )
? new DirectoryInfo( targetDirName )
: target.CreateSubdirectory( dir.Name );
CopyFilesRecursively( dir, targetDir );
}
foreach (FileInfo file in source.GetFiles()) {
if (file.Extension != ".meta") {
file.CopyTo( Path.Combine( target.FullName, file.Name ) );
}
}
}
/// <summary>
/// Obtain the build data directory and executable of the desired build,
/// depending on the specified target.
/// </summary>
/// <param name="target">Target platform.</param>
/// <param name="targetDataDir">Path of the data folder of the build,
/// as currently configured.</param>
/// <param name="targetExe">Path of the executable of the build,
/// as currently configured.</param>
private void GetTargetPaths(BuildTarget target, out string targetDataDir, out string targetExe) {
switch (target) {
case BuildTarget.StandaloneLinux:
targetDataDir = this.targetDir + "/" + this.execName + "_Data/";
targetExe = this.targetDir + "/" + this.execName;
break;
case BuildTarget.StandaloneLinux64:
targetDataDir = this.targetDir + "/" + this.execName + "_Data/";
targetExe = this.targetDir + "/" + this.execName;
break;
case BuildTarget.StandaloneLinuxUniversal:
targetDataDir = this.targetDir + "/" + this.execName + "_Data/";
targetExe = this.targetDir + "/" + this.execName;
break;
case BuildTarget.StandaloneOSXIntel:
targetDataDir = this.targetDir + "/" + this.execName + ".app" + "/Contents/";
targetExe = this.targetDir + "/" + this.execName + ".app";
break;
case BuildTarget.StandaloneOSXIntel64:
targetDataDir = this.targetDir + "/" + this.execName + ".app" + "/Contents/";
targetExe = this.targetDir + "/" + this.execName + ".app";
break;
case BuildTarget.StandaloneOSXUniversal:
targetDataDir = this.targetDir + "/" + this.execName + ".app" + "/Contents/";
targetExe = this.targetDir + "/" + this.execName + ".app";
break;
case BuildTarget.StandaloneWindows:
targetDataDir = this.targetDir + "/" + this.execName + "_Data/";
targetExe = this.targetDir + "/" + this.execName + ".exe";
break;
case BuildTarget.StandaloneWindows64:
targetDataDir = this.targetDir + "/" + this.execName + "_Data/";
targetExe = this.targetDir + "/" + this.execName + ".exe";
break;
default:
Debug.LogWarning( "No data dir for target " + target );
targetDataDir = null;
targetExe = this.targetDir;
break;
}
}
}
}