Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
PackageBuilder.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.IO;
5 using System.Linq;
6 using System.ServiceModel;
7 
8 using SiliconStudio.Assets.Compiler;
9 using SiliconStudio.Assets.Diagnostics;
10 using SiliconStudio.BuildEngine;
11 using SiliconStudio.Core;
12 using SiliconStudio.Core.Extensions;
13 using SiliconStudio.Core.Diagnostics;
14 using SiliconStudio.Core.IO;
15 using SiliconStudio.Core.MicroThreading;
16 using SiliconStudio.Core.Serialization.Assets;
17 
18 using System.Threading;
19 
20 namespace SiliconStudio.Assets.CompilerApp
21 {
22  public class PackageBuilder
23  {
24  private readonly PackageBuilderOptions builderOptions;
25  private RemoteLogForwarder assetLogger;
26  private Builder builder;
27 
28  public PackageBuilder(PackageBuilderOptions packageBuilderOptions)
29  {
30  if (packageBuilderOptions == null) throw new ArgumentNullException("packageBuilderOptions");
31 
32  builderOptions = packageBuilderOptions;
33  }
34 
36  {
37  BuildResultCode result;
38 
39  if (builderOptions.IsValidForSlave())
40  {
41  // Sleeps one second so that debugger can attach
42  //Thread.Sleep(1000);
43 
44  result = BuildSlave();
45  }
46  else
47  {
48  // build the project to the build path
49  result = BuildMaster();
50  }
51 
52  return result;
53  }
54 
55  private static void PrepareDatabases()
56  {
57  AssetManager.GetFileProvider = () => IndexFileCommand.DatabaseFileProvider.Value;
58  }
59 
60  private BuildResultCode BuildMaster()
61  {
62  assetLogger = new RemoteLogForwarder(builderOptions.Logger, builderOptions.LogPipeNames);
63  GlobalLogger.GlobalMessageLogged += assetLogger;
64 
65  // TODO handle solution file + package-id ?
66 
67  // When the current platform is not on windows, we need to make sure that all plugins are build, so we
68  // setup auto-compile when loading the session
69  var sessionLoadParameters = new PackageLoadParameters()
70  {
71  AutoCompileProjects = builderOptions.Platform != PlatformType.Windows || builderOptions.ProjectConfiguration != "Debug", // Avoid compiling if Windows|Debug
72  ExtraCompileProperties = builderOptions.ExtraCompileProperties,
73  };
74 
75  // Loads the root Package
76  var projectSessionResult = PackageSession.Load(builderOptions.PackageFile, sessionLoadParameters);
77  if (projectSessionResult.HasErrors)
78  {
79  projectSessionResult.CopyTo(builderOptions.Logger);
80  return BuildResultCode.BuildError;
81  }
82 
83  var projectSession = projectSessionResult.Session;
84 
85  // Check build configuration
86  var package = projectSession.LocalPackages.First();
87 
88  // Check build profile
89  var buildProfile = package.Profiles.FirstOrDefault(pair => pair.Name == builderOptions.BuildProfile);
90  if (buildProfile == null)
91  {
92  builderOptions.Logger.Error("Unable to find profile [{0}] in package [{1}]", builderOptions.BuildProfile, package.FullPath);
93  return BuildResultCode.BuildError;
94  }
95 
96  // Setup variables
97  var buildDirectory = builderOptions.BuildDirectory;
98  var outputDirectory = builderOptions.OutputDirectory;
99 
100  // Builds the project
101  var assetBuilder = new PackageAssetsCompiler(projectSession);
102  assetBuilder.AssetCompiled += RegisterBuildStepProcessedHandler;
103 
104  // Create context
105  var context = new AssetCompilerContext
106  {
107  Package = package,
108  Platform = builderOptions.Platform
109  };
110  // If a build profile is available, output the properties
111  context.Properties.Set(SiliconStudio.Paradox.Assets.ParadoxConfig.GraphicsPlatform, builderOptions.GraphicsPlatform.HasValue ? builderOptions.GraphicsPlatform.Value : builderOptions.GetDefaultGraphicsPlatform());
112  foreach (var propertyValue in buildProfile.Properties)
113  {
114  context.Properties.Set(propertyValue.Key, propertyValue.Value);
115  }
116 
117  var assetBuildResult = assetBuilder.Compile(context);
118  assetBuildResult.CopyTo(builderOptions.Logger);
119  if (assetBuildResult.HasErrors)
120  return BuildResultCode.BuildError;
121 
122  // Create the builder
123  var indexName = "index." + builderOptions.BuildProfile;
124  builder = new Builder(buildDirectory, builderOptions.BuildProfile, indexName, "InputHashes", builderOptions.Logger) { ThreadCount = builderOptions.ThreadCount };
125  builder.MonitorPipeNames.AddRange(builderOptions.MonitorPipeNames);
126 
127  // Add build steps generated by AssetBuilder
128  builder.Root.Add(assetBuildResult.BuildSteps);
129 
130  // Run builder
131  var result = builder.Run(Builder.Mode.Build);
132  builder.WriteIndexFile(false);
133 
134  // Fill list of bundles
135  var bundlePacker = new BundlePacker();
136  bundlePacker.Build(builderOptions.Logger, projectSession, buildProfile, indexName, outputDirectory, builder.DisableCompressionIds);
137 
138  // Flush and close logger
139  GlobalLogger.GlobalMessageLogged -= assetLogger;
140  assetLogger.Dispose();
141 
142  return result;
143  }
144 
145  private void RegisterBuildStepProcessedHandler(object sender, AssetCompiledArgs e)
146  {
147  if (e.Result.BuildSteps == null)
148  return;
149 
150  foreach (var buildStep in e.Result.BuildSteps.SelectDeep(x => x is EnumerableBuildStep && ((EnumerableBuildStep)x).Steps != null ? ((EnumerableBuildStep)x).Steps : Enumerable.Empty<BuildStep>()))
151  {
152  buildStep.Tag = e.Asset;
153  buildStep.StepProcessed += BuildStepProcessed;
154  }
155  }
156 
157  private void BuildStepProcessed(object sender, BuildStepEventArgs e)
158  {
159  var assetItem = (AssetItem)e.Step.Tag;
160  var assetRef = assetItem.ToReference();
161  var project = assetItem.Package;
162  var stepLogger = e.Logger is BuildStepLogger ? ((BuildStepLogger)e.Logger).StepLogger : null;
163  if (stepLogger != null)
164  {
165  foreach (var message in stepLogger.Messages.Where(x => x.LogMessage.IsAtLeast(LogMessageType.Warning)))
166  {
167  var assetMessage = new AssetLogMessage(project, assetRef, message.LogMessage.Type, AssetMessageCode.InternalCompilerError, assetRef.Location, message.LogMessage.Text)
168  {
169  Exception = message.LogMessage is LogMessage ? ((LogMessage)message.LogMessage).Exception : null
170  };
171  builderOptions.Logger.Log(assetMessage);
172  }
173  }
174  switch (e.Step.Status)
175  {
176  // This case should never happen
177  case ResultStatus.NotProcessed:
178  builderOptions.Logger.Log(new AssetLogMessage(project, assetRef, LogMessageType.Fatal, AssetMessageCode.InternalCompilerError, assetRef.Location));
179  break;
180  case ResultStatus.Successful:
181  builderOptions.Logger.Log(new AssetLogMessage(project, assetRef, LogMessageType.Verbose, AssetMessageCode.CompilationSucceeded, assetRef.Location));
182  break;
183  case ResultStatus.Failed:
184  builderOptions.Logger.Log(new AssetLogMessage(project, assetRef, LogMessageType.Error, AssetMessageCode.CompilationFailed, assetRef.Location));
185  break;
186  case ResultStatus.Cancelled:
187  builderOptions.Logger.Log(new AssetLogMessage(project, assetRef, LogMessageType.Verbose, AssetMessageCode.CompilationCancelled, assetRef.Location));
188  break;
189  case ResultStatus.NotTriggeredWasSuccessful:
190  builderOptions.Logger.Log(new AssetLogMessage(project, assetRef, LogMessageType.Verbose, AssetMessageCode.AssetUpToDate, assetRef.Location));
191  break;
192  case ResultStatus.NotTriggeredPrerequisiteFailed:
193  builderOptions.Logger.Log(new AssetLogMessage(project, assetRef, LogMessageType.Error, AssetMessageCode.PrerequisiteFailed, assetRef.Location));
194  break;
195  default:
196  throw new ArgumentOutOfRangeException();
197  }
198  e.Step.StepProcessed -= BuildStepProcessed;
199  }
200 
201  private static void RegisterRemoteLogger(IProcessBuilderRemote processBuilderRemote)
202  {
203  // 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)
204  // ReSharper disable EmptyGeneralCatchClause
205  GlobalLogger.GlobalMessageLogged += logMessage =>
206  {
207  try
208  {
209  var assetMessage = logMessage as AssetLogMessage;
210  var message = assetMessage != null ? new AssetSerializableLogMessage(assetMessage) : new SerializableLogMessage((LogMessage)logMessage);
211 
212  processBuilderRemote.ForwardLog(message);
213  } catch { }
214  };
215  // ReSharper restore EmptyGeneralCatchClause
216  }
217 
218  private BuildResultCode BuildSlave()
219  {
220  // Mount build path
221  ((FileSystemProvider)VirtualFileSystem.ApplicationData).ChangeBasePath(builderOptions.BuildDirectory);
222 
223  PrepareDatabases();
224 
225  VirtualFileSystem.CreateDirectory("/data/");
226  VirtualFileSystem.CreateDirectory("/data/db/");
227 
228  // Open WCF channel with master builder
229  var namedPipeBinding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None) { SendTimeout = TimeSpan.FromSeconds(300.0) };
230  var processBuilderRemote = ChannelFactory<IProcessBuilderRemote>.CreateChannel(namedPipeBinding, new EndpointAddress(builderOptions.SlavePipe));
231 
232  try
233  {
234  RegisterRemoteLogger(processBuilderRemote);
235 
236  // Create scheduler
237  var scheduler = new Scheduler();
238 
239  var status = ResultStatus.NotProcessed;
240 
241  // Schedule command
242  string buildPath = builderOptions.BuildDirectory;
243  string buildProfile = builderOptions.BuildProfile;
244 
245  Builder.SetupBuildPath(buildPath);
246 
247  Logger logger = builderOptions.Logger;
248  MicroThread microthread = scheduler.Add(async () =>
249  {
250  // Deserialize command and parameters
251  Command command = processBuilderRemote.GetCommandToExecute();
252  BuildParameterCollection parameters = processBuilderRemote.GetBuildParameters();
253 
254  // Run command
255  var inputHashes = FileVersionTracker.GetDefault();
256  var builderContext = new BuilderContext(buildPath, buildProfile, inputHashes, parameters, 0, null);
257 
258  var commandContext = new RemoteCommandContext(processBuilderRemote, command, builderContext, logger);
259  IndexFileCommand.MountDatabases(commandContext);
260  command.PreCommand(commandContext);
261  status = await command.DoCommand(commandContext);
262  command.PostCommand(commandContext, status);
263 
264  // Returns result to master builder
265  processBuilderRemote.RegisterResult(commandContext.ResultEntry);
266  });
267 
268  while (true)
269  {
270  scheduler.Run();
271 
272  // Exit loop if no more micro threads
273  lock (scheduler.MicroThreads)
274  {
275  if (!scheduler.MicroThreads.Any())
276  break;
277  }
278 
279  Thread.Sleep(0);
280  }
281 
282  // Rethrow any exception that happened in microthread
283  if (microthread.Exception != null)
284  {
285  builderOptions.Logger.Fatal(microthread.Exception.ToString());
286  return BuildResultCode.BuildError;
287  }
288 
289  if (status == ResultStatus.Successful || status == ResultStatus.NotTriggeredWasSuccessful)
290  return BuildResultCode.Successful;
291 
292  return BuildResultCode.BuildError;
293  }
294  finally
295  {
296  // Close WCF channel
297  // ReSharper disable SuspiciousTypeConversion.Global
298  ((IClientChannel)processBuilderRemote).Close();
299  // ReSharper restore SuspiciousTypeConversion.Global
300  }
301  }
302 
303  /// <summary>
304  /// Cancels this build.
305  /// </summary>
306  /// <returns><c>true</c> if the build was cancelled, <c>false</c> otherwise.</returns>
307  public bool Cancel()
308  {
309  if (builder != null && builder.IsRunning)
310  {
311  builder.CancelBuild();
312  return true;
313  }
314  return false;
315  }
316  }
317 }
A BuildStep that can spawn multiple BuildStep. Input and output tracking and merging will be performe...
Virtual abstraction over a file system. It handles access to files, http, packages, path rewrite, etc...
A package assets compiler. Creates the build steps necessary to produce the assets of a package...
Represents an execution context managed by a Scheduler, that can cooperatively yield execution to ano...
Definition: MicroThread.cs:16
GraphicsPlatform
The graphics platform.
A file system implementation for IVirtualFileProvider.
AssetMessageCode
A message code used by AssetLogMessage to identify an error/warning.
The template can be applied to an existing Assets.Package.
ResultStatus
Status of a command.
Definition: ResultStatus.cs:8
The context used when compiling an asset in a Package.
ListBuildStep BuildSteps
Gets or sets the build steps generated for the build engine. This can be null if LoggerResult.HasErrors is true.
Base implementation for ILogger.
Definition: Logger.cs:10
Exception Exception
Gets the exception that was thrown by this MicroThread.
Definition: MicroThread.cs:133
object Tag
A tag property that can contain anything useful for tools based on this build Engine.
Definition: BuildStep.cs:65
static readonly IVirtualFileProvider ApplicationData
The application data file provider.
static MicroThreadLocal< DatabaseFileProvider > DatabaseFileProvider
A Command that reads and/or writes to the index file.
A class that represents a copy of a LogMessage that can be serialized.
LogMessageType
Type of a LogMessage.
PackageBuilder(PackageBuilderOptions packageBuilderOptions)
A base log message used by the logging infrastructure.
Definition: LogMessage.cs:13
AssetCompilerResult Result
The result of the asset compilation.
Platform specific queries and functions.
Definition: Platform.cs:15
Provides a specialized LogMessage to give specific information about an asset.
Scheduler that manage a group of cooperating MicroThread.
Definition: Scheduler.cs:20
The class represents the argument of the ItemListCompiler.AssetCompiled event raised by the ItemListC...
Parameters used for loading a package.