Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
BuildEngineCommands.cs
Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Diagnostics;
4 using System.IO;
5 using System.Linq;
6 using System.ServiceModel;
7 
8 using Mono.Options;
9 using SiliconStudio.Core.Storage;
10 using SiliconStudio.Core.Diagnostics;
11 using SiliconStudio.Core.IO;
12 using SiliconStudio.Core.MicroThreading;
13 using SiliconStudio.Core.Serialization;
14 using SiliconStudio.Core.Serialization.Assets;
15 using SiliconStudio.Core.Serialization.Contents;
16 using System.Threading;
17 
18 namespace SiliconStudio.BuildEngine
19 {
20  public static class BuildEngineCommands
21  {
22  public static BuildResultCode Build(BuilderOptions options)
23  {
24  BuildResultCode result;
25 
26  if (options.IsValidForSlave())
27  {
28  // Sleeps one second so that debugger can attach
29  //Thread.Sleep(1000);
30 
31  result = BuildSlave(options);
32  }
33  else if (options.IsValidForMaster())
34  {
35  result = BuildLocal(options);
36 
37  if (!string.IsNullOrWhiteSpace(options.OutputDirectory) && options.BuilderMode == Builder.Mode.Build)
38  {
39  CopyBuildToOutput(options);
40  }
41  }
42  else
43  {
44  throw new OptionException("Insufficient parameters, no action taken", "build-path");
45  }
46 
47  return result;
48  }
49 
50  private static void PrepareDatabases(BuilderOptions options)
51  {
52  AssetManager.GetDatabaseFileProvider = () => IndexFileCommand.DatabaseFileProvider.Value;
53  }
54 
55  public static BuildResultCode BuildLocal(BuilderOptions options)
56  {
57  string inputFile = options.InputFiles[0];
58  string sdkDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../..");
59 
60  BuildScript buildScript = BuildScript.LoadFromFile(sdkDir, inputFile);
61  buildScript.Compile(options.Plugins);
62 
63  if (buildScript.GetWarnings().FirstOrDefault() != null)
64  {
65  foreach (string warning in buildScript.GetWarnings())
66  {
67  options.Logger.Warning(warning);
68  }
69  }
70 
71  if (buildScript.HasErrors)
72  {
73  foreach (string error in buildScript.GetErrors())
74  {
75  options.Logger.Error(error);
76  }
77  throw new InvalidOperationException("Can't compile the provided build script.");
78  }
79 
80  string inputDir = Path.GetDirectoryName(inputFile) ?? Environment.CurrentDirectory;
81  options.SourceBaseDirectory = options.SourceBaseDirectory ?? Path.Combine(inputDir, buildScript.SourceBaseDirectory ?? "");
82  options.BuildDirectory = options.BuildDirectory ?? Path.Combine(inputDir, buildScript.BuildDirectory ?? "");
83  options.OutputDirectory = options.OutputDirectory ?? (buildScript.OutputDirectory != null ? Path.Combine(inputDir, buildScript.OutputDirectory) : "");
84  options.MetadataDatabaseDirectory = options.MetadataDatabaseDirectory ?? (buildScript.MetadataDatabaseDirectory != null ? Path.Combine(inputDir, buildScript.MetadataDatabaseDirectory) : "");
85  if (!string.IsNullOrWhiteSpace(options.SourceBaseDirectory))
86  {
87  if (!Directory.Exists(options.SourceBaseDirectory))
88  {
89  string error = string.Format("Source base directory \"{0}\" does not exists.", options.SourceBaseDirectory);
90  options.Logger.Error(error);
91  throw new OptionException(error, "sourcebase");
92  }
93  Environment.CurrentDirectory = options.SourceBaseDirectory;
94  }
95 
96  if (string.IsNullOrWhiteSpace(options.BuildDirectory))
97  {
98  throw new OptionException("This tool requires a build path.", "build-path");
99  }
100 
101  // Mount build path
102  ((FileSystemProvider)VirtualFileSystem.ApplicationData).ChangeBasePath(options.BuildDirectory);
103 
104  options.ValidateOptionsForMaster();
105 
106  // assets is always added by default
107  //options.Databases.Add(new DatabaseMountInfo("/assets"));
108  PrepareDatabases(options);
109 
110  try
111  {
112  VirtualFileSystem.CreateDirectory("/data/");
113  VirtualFileSystem.CreateDirectory("/data/db/");
114  }
115  catch (Exception)
116  {
117  throw new OptionException("Invalid Build database path", "database");
118  }
119 
120  // Create builder
121  LogMessageType logLevel = options.Debug ? LogMessageType.Debug : (options.Verbose ? LogMessageType.Verbose : LogMessageType.Info);
122  var logger = Logger.GetLogger("builder");
123  logger.ActivateLog(logLevel);
124  var builder = new Builder("builder", options.BuildDirectory, options.BuilderMode, logger) { ThreadCount = options.ThreadCount };
125  builder.MonitorPipeNames.AddRange(options.MonitorPipeNames);
126  builder.ActivateConfiguration(options.Configuration);
127  foreach (var sourceFolder in buildScript.SourceFolders)
128  {
129  builder.InitialVariables.Add(("SourceFolder:" + sourceFolder.Key).ToUpperInvariant(), sourceFolder.Value);
130  }
131  Console.CancelKeyPress += (sender, e) => Cancel(builder, e);
132 
133  buildScript.Execute(builder);
134 
135  // Run builder
136  return builder.Run(options.Append == false);
137  }
138 
139  private static void RegisterRemoteLogger(IProcessBuilderRemote processBuilderRemote)
140  {
141  // The pipe might be broken while we try to output log, so let's try/catch the call to prevent program for crashing here (it should crash at a proper location anyway if the pipe is broken/closed)
142  // ReSharper disable EmptyGeneralCatchClause
143  GlobalLogger.MessageLogged += msg => { try { processBuilderRemote.ForwardLog(msg); } catch { } };
144  // ReSharper restore EmptyGeneralCatchClause
145  }
146 
148  {
149  // Mount build path
150  ((FileSystemProvider)VirtualFileSystem.ApplicationData).ChangeBasePath(options.BuildDirectory);
151 
152  PrepareDatabases(options);
153 
154  try
155  {
156  VirtualFileSystem.CreateDirectory("/data/");
157  VirtualFileSystem.CreateDirectory("/data/db/");
158  }
159  catch (Exception)
160  {
161  throw new OptionException("Invalid Build database path", "database");
162  }
163 
164  // Open WCF channel with master builder
165  var namedPipeBinding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None) { SendTimeout = TimeSpan.FromSeconds(300.0) };
166  var processBuilderRemote = ChannelFactory<IProcessBuilderRemote>.CreateChannel(namedPipeBinding, new EndpointAddress(options.SlavePipe));
167 
168  try
169  {
170  RegisterRemoteLogger(processBuilderRemote);
171 
172  // Create scheduler
173  var scheduler = new Scheduler();
174 
175  var status = ResultStatus.NotProcessed;
176 
177  // Schedule command
178  string buildPath = options.BuildDirectory;
179  Logger logger = options.Logger;
180  MicroThread microthread = scheduler.Add(async () =>
181  {
182  // Deserialize command and parameters
183  Command command = processBuilderRemote.GetCommandToExecute();
184  BuildParameterCollection parameters = processBuilderRemote.GetBuildParameters();
185 
186  // Run command
187  var inputHashes = new DictionaryStore<InputVersionKey, ObjectId>(VirtualFileSystem.OpenStream("/data/db/InputHashes", VirtualFileMode.OpenOrCreate, VirtualFileAccess.ReadWrite, VirtualFileShare.ReadWrite));
188  var builderContext = new BuilderContext(buildPath, inputHashes, parameters, 0, null);
189 
190  var commandContext = new RemoteCommandContext(processBuilderRemote, command, builderContext, logger);
191  command.PreCommand(commandContext);
192  status = await command.DoCommand(commandContext);
193  command.PostCommand(commandContext, status);
194 
195  // Returns result to master builder
196  processBuilderRemote.RegisterResult(commandContext.ResultEntry);
197  });
198 
199  while (true)
200  {
201  scheduler.Run();
202 
203  // Exit loop if no more micro threads
204  lock (scheduler.MicroThreads)
205  {
206  if (!scheduler.MicroThreads.Any())
207  break;
208  }
209 
210  Thread.Sleep(0);
211  }
212 
213  // Rethrow any exception that happened in microthread
214  if (microthread.Exception != null)
215  {
216  options.Logger.Fatal(microthread.Exception.ToString());
217  return BuildResultCode.BuildError;
218  }
219 
220  if (status == ResultStatus.Successful || status == ResultStatus.NotTriggeredWasSuccessful)
221  return BuildResultCode.Successful;
222 
223  return BuildResultCode.BuildError;
224  }
225  finally
226  {
227  // Close WCF channel
228  // ReSharper disable SuspiciousTypeConversion.Global
229  ((IClientChannel)processBuilderRemote).Close();
230  // ReSharper restore SuspiciousTypeConversion.Global
231  }
232  }
233 
234  public static void CopyBuildToOutput(BuilderOptions options)
235  {
236  throw new InvalidOperationException();
237  }
238 
239  private static void Collect(HashSet<ObjectId> objectIds, ObjectId objectId, IAssetIndexMap assetIndexMap)
240  {
241  // Already added?
242  if (!objectIds.Add(objectId))
243  return;
244 
245  using (var stream = AssetManager.FileProvider.OpenStream("obj/" + objectId, VirtualFileMode.Open, VirtualFileAccess.Read))
246  {
247  // Read chunk header
248  var streamReader = new BinarySerializationReader(stream);
249  var header = ChunkHeader.Read(streamReader);
250 
251  // Only process chunks
252  if (header != null)
253  {
254  if (header.OffsetToReferences != -1)
255  {
256  // Seek to where references are stored and deserialize them
257  streamReader.NativeStream.Seek(header.OffsetToReferences, SeekOrigin.Begin);
258 
259  List<ChunkReference> references = null;
260  streamReader.Serialize(ref references, ArchiveMode.Deserialize);
261 
262  foreach (var reference in references)
263  {
264  ObjectId refObjectId;
265  var databaseFileProvider = DatabaseFileProvider.ResolveObjectId(reference.Location, out refObjectId);
266  if (databaseFileProvider != null)
267  {
268  Collect(objectIds, refObjectId, databaseFileProvider.AssetIndexMap);
269  }
270  }
271  }
272  }
273  }
274  }
275 
276  public static void Cancel(Builder builder, ConsoleCancelEventArgs e)
277  {
278  if (builder != null && builder.IsRunning)
279  {
280  e.Cancel = true;
281  builder.CancelBuild();
282  }
283  }
284  }
285 }
static BuildResultCode BuildSlave(BuilderOptions options)
Virtual abstraction over a file system. It handles access to files, http, packages, path rewrite, etc...
Represents an execution context managed by a Scheduler, that can cooperatively yield execution to ano...
Definition: MicroThread.cs:16
static void CopyBuildToOutput(BuilderOptions options)
A file system implementation for IVirtualFileProvider.
ResultStatus
Status of a command.
Definition: ResultStatus.cs:8
bool IsRunning
Indicate whether this builder is currently running.
Definition: Builder.cs:131
bool IsValidForSlave()
This function indicate if the current builder options mean to execute a slave session ...
Base implementation for ILogger.
Definition: Logger.cs:10
Exception Exception
Gets the exception that was thrown by this MicroThread.
Definition: MicroThread.cs:133
Mode
Indicate which mode to use with this builder
Definition: Builder.cs:69
static readonly IVirtualFileProvider ApplicationData
The application data file provider.
static MicroThreadLocal< DatabaseFileProvider > DatabaseFileProvider
A Command that reads and/or writes to the index file.
Implements SerializationStream as a binary reader.
static BuildResultCode BuildLocal(BuilderOptions options)
static BuildResultCode Build(BuilderOptions options)
LogMessageType
Type of a LogMessage.
static void Cancel(Builder builder, ConsoleCancelEventArgs e)
A hash to uniquely identify data.
Definition: ObjectId.cs:13
Scheduler that manage a group of cooperating MicroThread.
Definition: Scheduler.cs:20
bool IsValidForMaster()
This function indicate if the current builder options mean to execute a master session ...