3 using SiliconStudio.Core;
4 using SiliconStudio.Core.Storage;
6 using System.Collections.Generic;
9 using System.Threading;
10 using System.Threading.Tasks;
11 using SiliconStudio.Core.Diagnostics;
12 using SiliconStudio.Core.MicroThreading;
13 using SiliconStudio.Core.Serialization.Assets;
14 using SiliconStudio.Core.IO;
16 using System.Reflection;
18 namespace SiliconStudio.BuildEngine
22 private readonly
int[] stepResults;
23 public int Total {
get;
private set; }
27 stepResults =
new int[Enum.GetValues(typeof(
ResultStatus)).Length];
35 ++stepResults[(int)result];
43 return stepResults[(int)result];
52 foreach (var value
in Enum.GetValues(typeof(
ResultStatus)))
53 stepResults[(
int)value] = 0;
60 public const int ExpectedVersion = 3;
61 public static readonly
string DoNotPackTag =
"DoNotPack";
62 public static readonly
string DoNotCompressTag =
"DoNotCompress";
64 #region Public Members
93 public string BuilderName {
get; set; }
98 public Guid BuilderId {
get;
private set; }
103 public string SlaveBuilderPath {
get; set; }
108 public int ThreadCount {
109 get {
return threadCount; }
110 set { threadCount = value;
if (MaxParallelProcesses > value) MaxParallelProcesses = value; }
112 private int threadCount;
117 public int MaxParallelProcesses {
118 get {
return maxParallelProcesses; }
119 set { maxParallelProcesses = value;
if (value > ThreadCount)
throw new InvalidOperationException(
"MaxParallelProcesses can't be greater than ThreadCount."); }
121 private int maxParallelProcesses;
131 public bool IsRunning {
get;
protected set; }
136 public bool Cancelled {
get;
protected set; }
138 public List<string> MonitorPipeNames {
get;
private set; }
140 public const string MonitorPipeName =
"net.pipe://localhost/Paradox.BuildEngine.Monitor";
144 public string MetadataDatabaseDirectory {
get; set; }
146 public readonly ISet<ObjectId> DisableCompressionIds =
new HashSet<ObjectId>();
148 #endregion Public Members
149 #region Private Members
154 private readonly
string indexFilename;
159 private readonly
string inputHashesFilename;
164 private readonly
string buildPath;
169 private readonly
string buildProfile;
174 private const string DatabasePath =
"/data/db/";
179 private CancellationTokenSource cancellationTokenSource;
183 private readonly CommandIOMonitor ioMonitor;
184 private readonly List<BuildThreadMonitor> threadMonitors =
new List<BuildThreadMonitor>();
191 private readonly DateTime startTime;
198 private Mode runMode;
200 #endregion Private Members
205 private string IndexFileFullPath
207 get {
return DatabasePath + indexFilename; }
213 private string InputHashesFileFullPath
215 get {
return DatabasePath + inputHashesFilename; }
218 public Builder(
string buildPath,
string buildProfile,
string indexFilename,
string inputHashesFilename,
ILogger logger)
220 if (buildPath == null)
throw new ArgumentNullException(
"buildPath");
221 if (indexFilename == null)
throw new ArgumentNullException(
"indexFilename");
222 if (inputHashesFilename == null)
throw new ArgumentNullException(
"inputHashesFilename");
224 MonitorPipeNames =
new List<string>();
225 startTime = DateTime.Now;
226 this.buildProfile = buildProfile;
227 this.indexFilename = indexFilename;
228 var entryAssembly = Assembly.GetEntryAssembly();
229 SlaveBuilderPath = entryAssembly != null ? entryAssembly.Location :
"";
231 this.inputHashesFilename = inputHashesFilename;
232 this.buildPath = buildPath;
234 ioMonitor =
new CommandIOMonitor(
Logger);
235 ThreadCount = Environment.ProcessorCount;
236 MaxParallelProcesses = ThreadCount;
237 BuilderId = Guid.NewGuid();
238 InitialVariables =
new Dictionary<string, string>();
240 SetupBuildPath(buildPath);
242 var objectDatabase = IndexFileCommand.ObjectDatabase;
245 int currentVersion = 0;
246 var versionFile = Path.Combine(VirtualFileSystem.GetAbsolutePath(DatabasePath),
"version");
247 if (
File.Exists(versionFile))
251 var versionText = File.ReadAllText(versionFile);
252 currentVersion = int.Parse(versionText);
259 if (currentVersion != ExpectedVersion)
261 var looseObjects = objectDatabase.EnumerateLooseObjects().ToArray();
263 if (looseObjects.Length > 0)
265 Logger.Info(
"Database version number has been updated from {0} to {1}, erasing all objects...", currentVersion, ExpectedVersion);
268 foreach (var objectId
in looseObjects)
272 objectDatabase.Delete(objectId);
281 File.WriteAllText(versionFile, ExpectedVersion.ToString());
286 var databasePathSplits = DatabasePath.Split(
'/');
287 var accumulatorPath =
"/";
288 foreach (var pathPart
in databasePathSplits.Where(x=>x!=
""))
290 accumulatorPath += pathPart +
"/";
291 VirtualFileSystem.CreateDirectory(accumulatorPath);
293 accumulatorPath +=
"";
302 IndexFileCommand.ObjectDatabase =
new ObjectDatabase(DatabasePath, loadDefaultBundle:
false);
309 private readonly BuildTransaction buildTransaction;
310 private readonly
Logger logger;
311 private readonly
Builder builder;
316 this.builderContext = builderContext;
317 this.builder = builder;
318 this.buildStep = buildStep;
324 public ObjectDatabase ResultMap {
get {
return builder.resultMap; } }
326 public CancellationTokenSource CancellationTokenSource {
get {
return builder.cancellationTokenSource; } }
328 public Dictionary<string, string> Variables {
get; set; }
330 public IMetadataProvider MetadataProvider {
get {
return builderContext.MetadataProvider; } }
332 public void ScheduleBuildStep(BuildStep step)
334 builder.ScheduleBuildStep(builderContext, buildStep, step, Variables);
339 return buildStep.GetOutputObjectsGroups();
344 var hash = ObjectId.Empty;
349 hash = builderContext.InputHashes.ComputeFileHash(filePath);
351 case UrlType.Internal:
352 if (!buildTransaction.TryGetValue(filePath, out hash))
353 Logger.
Warning(
"Location " + filePath +
" does not exist currently and is required to compute the current command hash. The build cache will not work for this command!");
355 case UrlType.Virtual:
356 var providerResult = VirtualFileSystem.ResolveProvider(filePath,
true);
358 var microProvider = providerResult.Provider as MicroThreadFileProvider;
359 if (microProvider != null)
361 dbProvider = microProvider.ThreadLocal.Value as DatabaseFileProvider;
364 if (dbProvider != null)
366 dbProvider.AssetIndexMap.TryGetValue(providerResult.Path, out hash);
374 public CommandBuildStep IsCommandCurrentlyRunning(
ObjectId commandHash)
376 lock (builderContext.CommandsInProgress)
378 CommandBuildStep step;
379 builderContext.CommandsInProgress.TryGetValue(commandHash, out step);
384 public void NotifyCommandBuildStepStarted(CommandBuildStep commandBuildStep,
ObjectId commandHash)
386 lock (builderContext.CommandsInProgress)
388 if (!builderContext.CommandsInProgress.ContainsKey(commandHash))
389 builderContext.CommandsInProgress.Add(commandHash, commandBuildStep);
391 builder.ioMonitor.CommandStarted(commandBuildStep);
395 public void NotifyCommandBuildStepFinished(CommandBuildStep commandBuildStep,
ObjectId commandHash)
397 lock (builderContext.CommandsInProgress)
399 builderContext.CommandsInProgress.Remove(commandHash);
400 builder.ioMonitor.CommandEnded(commandBuildStep);
405 private void ScheduleBuildStep(BuilderContext builderContext, BuildStep instigator, BuildStep buildStep,
IDictionary<string, string> variables)
407 if (buildStep.ExecutionId == 0)
409 if (buildStep.Parent != null && buildStep.Parent != instigator)
410 throw new InvalidOperationException(
"Scheduling a BuildStep with a different instigator that its parent");
411 if (buildStep.Parent == null)
413 buildStep.Parent = instigator;
416 var executeContext =
new ExecuteContext(
this, builderContext, buildStep) { Variables =
new Dictionary<string, string>(variables) };
419 if (runMode == Mode.Build)
424 var buildStepPriority = buildStep;
425 while (buildStepPriority != null)
427 if (buildStepPriority.Priority.HasValue)
429 microThread.Priority = buildStepPriority.Priority.Value;
433 buildStepPriority = buildStepPriority.Parent;
436 buildStep.ExecutionId = microThread.Id;
438 foreach (var threadMonitor
in threadMonitors)
440 threadMonitor.RegisterBuildStep(buildStep, ((BuildStepLogger)executeContext.Logger).StepLogger);
443 microThread.Name = buildStep.ToString();
449 microThread.ScheduleMode = ScheduleMode.First;
451 microThread.Start(async () =>
454 await Task.WhenAll(buildStep.PrerequisiteSteps.Select(x => x.ExecutedAsync()).ToArray());
457 var status = ResultStatus.NotProcessed;
459 if (buildStep.ArePrerequisitesSuccessful)
463 IndexFileCommand.MountDatabases(executeContext);
466 status = await buildStep.Execute(executeContext, builderContext);
468 catch (TaskCanceledException e)
471 executeContext.Logger.Warning(
"A child task of build step " + buildStep +
" triggered a TaskCanceledException that was not caught by the parent task. The command has not handled cancellation gracefully.");
472 executeContext.Logger.Warning(e.Message);
473 status = ResultStatus.Cancelled;
477 executeContext.Logger.Error(
"Exception in command " + buildStep +
": " + e);
478 status = ResultStatus.Failed;
482 IndexFileCommand.UnmountDatabases(executeContext);
486 throw new InvalidDataException(
"The build step " + buildStep +
" returned ResultStatus.NotProcessed after completion.");
490 executeContext.Logger.Error(
"Exception in command " + buildStep +
": " + microThread.Exception);
491 status = ResultStatus.Failed;
496 status = ResultStatus.NotTriggeredPrerequisiteFailed;
499 buildStep.RegisterResult(executeContext, status);
500 stepCounter.AddStepResult(status);
506 var logType = LogMessageType.Info;
507 string logText = null;
509 switch (buildStep.Status)
511 case ResultStatus.Successful:
512 logType = LogMessageType.Info;
513 logText =
"BuildStep {0} was successful.".ToFormat(buildStep.ToString());
515 case ResultStatus.Failed:
516 logType = LogMessageType.Error;
517 logText =
"BuildStep {0} failed.".ToFormat(buildStep.ToString());
519 case ResultStatus.Cancelled:
520 logType = LogMessageType.Warning;
521 logText =
"BuildStep {0} cancelled.".ToFormat(buildStep.ToString());
523 case ResultStatus.NotProcessed:
524 throw new InvalidDataException(
"BuildStep has neither succeeded, failed, nor been cancelled");
528 var logMessage =
new LogMessage(buildStep.Module, logType, logText);
529 executeContext.Logger.Log(logMessage);
535 buildStep.Clean(executeContext, builderContext, runMode == Mode.CleanAndDelete);
548 cancellationTokenSource.Cancel();
554 foreach (var threadMonitor
in threadMonitors)
555 threadMonitor.RegisterThread(Thread.CurrentThread.ManagedThreadId);
562 lock (scheduler.MicroThreads)
564 if (!scheduler.MicroThreads.Any())
588 if (!mergeWithCurrentIndexFile)
592 VirtualFileSystem.FileDelete(IndexFileFullPath);
599 using (var indexFile = AssetIndexMap.NewTool(indexFilename))
603 Root.OutputObjects.Where(x => x.Key.Type == UrlType.Internal)
604 .Select(x =>
new KeyValuePair<string, ObjectId>(x.Key.Path, x.Value.ObjectId)));
606 foreach (var x
in Root.OutputObjects)
608 if(x.Key.Type !=
UrlType.Internal)
611 if (x.Value.Tags.Contains(DoNotCompressTag))
612 DisableCompressionIds.Add(x.Value.ObjectId);
625 throw new InvalidOperationException(
"An instance of this Builder is already running.");
629 cancellationTokenSource =
new CancellationTokenSource();
632 DisableCompressionIds.Clear();
635 var inputHashes = FileVersionTracker.GetDefault();
637 var builderContext =
new BuilderContext(buildPath, buildProfile, inputHashes, parameters, MaxParallelProcesses, SlaveBuilderPath);
638 if (!
string.IsNullOrWhiteSpace(MetadataDatabaseDirectory))
643 builderContext.MetadataProvider = metadataProvider;
647 resultMap = IndexFileCommand.ObjectDatabase;
652 threadMonitors.Add(
new BuildThreadMonitor(scheduler, BuilderId));
653 foreach (var monitorPipeName
in MonitorPipeNames)
654 threadMonitors.Add(
new BuildThreadMonitor(scheduler, BuilderId, monitorPipeName));
656 foreach (var threadMonitor
in threadMonitors)
657 threadMonitor.Start();
660 ScheduleBuildStep(builderContext, null, Root, InitialVariables);
663 var threads = Enumerable.Range(0, ThreadCount).Select(x =>
new Thread(
SafeAction.
Wrap(RunUntilEnd)) { IsBackground =
true }).ToArray();
667 foreach (var thread
in threads)
669 thread.Name =
"Builder thread " + (++threadId);
674 foreach (var thread
in threads)
679 foreach (var threadMonitor
in threadMonitors)
680 threadMonitor.Finish();
682 foreach (var threadMonitor
in threadMonitors)
683 threadMonitor.Join();
686 threadMonitors.Clear();
689 if (runMode ==
Mode.Build)
692 if (cancellationTokenSource.IsCancellationRequested)
694 Logger.Error(
"Build cancelled.");
695 result = BuildResultCode.Cancelled;
698 else if (stepCounter.Get(
ResultStatus.Failed) > 0 || stepCounter.Get(ResultStatus.NotTriggeredPrerequisiteFailed) > 0)
700 Logger.Error(
"Build finished in {0} steps. Command results: {1} succeeded, {2} up-to-date, {3} failed, {4} not triggered due to previous failure.",
701 stepCounter.Total, stepCounter.Get(ResultStatus.Successful), stepCounter.Get(
ResultStatus.NotTriggeredWasSuccessful),
702 stepCounter.Get(ResultStatus.Failed), stepCounter.Get(
ResultStatus.NotTriggeredPrerequisiteFailed));
704 Logger.Error(
"Build failed.");
705 result = BuildResultCode.BuildError;
709 Logger.Info(
"Build finished in {0} steps. Command results: {1} succeeded, {2} up-to-date, {3} failed, {4} not triggered due to previous failure.",
710 stepCounter.Total, stepCounter.Get(ResultStatus.Successful), stepCounter.Get(
ResultStatus.NotTriggeredWasSuccessful),
711 stepCounter.Get(ResultStatus.Failed), stepCounter.Get(
ResultStatus.NotTriggeredPrerequisiteFailed));
713 Logger.Info(
"Build is successful.");
714 result = BuildResultCode.Successful;
724 VirtualFileSystem.FileDelete(InputHashesFileFullPath);
728 return BuildResultCode.BuildError;
737 case Mode.CleanAndDelete:
738 modeName =
"Clean-and-delete";
741 throw new InvalidOperationException(
"Builder executed in unknown mode.");
744 if (cancellationTokenSource.IsCancellationRequested)
746 Logger.Error(modeName +
" has been cancelled.");
747 result = BuildResultCode.Cancelled;
750 else if (stepCounter.Get(
ResultStatus.Failed) > 0 || stepCounter.Get(ResultStatus.NotTriggeredPrerequisiteFailed) > 0)
752 Logger.Error(modeName +
" has failed.");
753 result = BuildResultCode.BuildError;
757 Logger.Error(modeName +
" has been successfully completed.");
758 result = BuildResultCode.Successful;
Virtual abstraction over a file system. It handles access to files, http, packages, path rewrite, etc...
IEnumerable< IDictionary< ObjectUrl, OutputObject > > GetOutputObjectsGroups()
Represents an execution context managed by a Scheduler, that can cooperatively yield execution to ano...
A file system implementation for IVirtualFileProvider.
void CancelBuild()
Cancel the currently executing build.
static ThreadStart Wrap(ThreadStart action, [CallerFilePath] string sourceFilePath="", [CallerMemberName] string memberName="", [CallerLineNumber] int sourceLineNumber=0)
void AddStepResult(ResultStatus result)
static bool FileExists(string path)
Checks the existence of a file.
ResultStatus
Status of a command.
int Get(ResultStatus result)
Gives access to the object database.
Base implementation for ILogger.
Exception Exception
Gets the exception that was thrown by this MicroThread.
ILogger Logger
Logger used by the builder and the commands
Mode
Indicate which mode to use with this builder
static readonly IVirtualFileProvider ApplicationData
The application data file provider.
static MicroThreadLocal< DatabaseFileProvider > DatabaseFileProvider
A Command that reads and/or writes to the index file.
static void SetupBuildPath(string buildPath)
A hash to uniquely identify data.
A base log message used by the logging infrastructure.
BuildResultCode Run(Mode mode, bool writeIndexFile=true, bool enableMonitor=true)
Runs this instance.
void WriteIndexFile(bool mergeWithCurrentIndexFile)
Write the generated objects into the index map file.
Builder(string buildPath, string buildProfile, string indexFilename, string inputHashesFilename, ILogger logger)
Scheduler that manage a group of cooperating MicroThread.
void Reset()
Discard the current Root build step and initialize a new empty one.
void Warning(string message, Exception exception, CallerInfo callerInfo=null)
Logs the specified warning message with an exception.