internal bool ShouldExecute(IExecuteContext executeContext, CommandResultEntry[] previousResultCollection, ObjectId commandHash, out CommandResultEntry matchingResult) { var outputObjectsGroups = executeContext.GetOutputObjectsGroups(); MicrothreadLocalDatabases.MountDatabase(outputObjectsGroups); try { matchingResult = FindMatchingResult(executeContext, previousResultCollection); } finally { MicrothreadLocalDatabases.UnmountDatabase(); } if (matchingResult == null || Command.ShouldForceExecution()) { // Ensure we ignore existing results if the execution is forced matchingResult = null; return(true); } return(false); }
private void ScheduleBuildStep(BuilderContext builderContext, BuildStep instigator, BuildStep buildStep, IDictionary <string, string> variables) { if (buildStep.ExecutionId == 0) { if (buildStep.Parent != null && buildStep.Parent != instigator) { throw new InvalidOperationException("Scheduling a BuildStep with a different instigator that its parent"); } if (buildStep.Parent == null) { buildStep.Parent = instigator; } // Compute content dependencies before scheduling the build GenerateDependencies(buildStep); // TODO: Big review of the log infrastructure of CompilerApp & BuildEngine! // Create a logger that redirects to various places (BuildStep.Logger, timestampped log, global log, etc...) var buildStepLogger = new BuildStepLogger(buildStep, Logger, startTime); var logger = (Logger)buildStepLogger; // Apply user-registered callbacks to the logger buildStep.TransformExecuteContextLogger?.Invoke(ref logger); // Create execute context var executeContext = new ExecuteContext(this, builderContext, buildStep, logger) { Variables = new Dictionary <string, string>(variables) }; //buildStep.ExpandStrings(executeContext); if (runMode == Mode.Build) { MicroThread microThread = scheduler.Create(); // Set priority from this build step, if we have one. if (buildStep.Priority.HasValue) { microThread.Priority = buildStep.Priority.Value; } buildStep.ExecutionId = microThread.Id; microThread.Name = buildStep.ToString(); // Default: // Schedule continuations as early as possible to help EnumerableBuildStep finish when all its task are finished. // Otherwise, it would wait for all leaf to finish first before finishing parent EnumerableBuildStep. // This should also reduce memory usage, and might improve cache coherency as well. microThread.ScheduleMode = ScheduleMode.First; microThread.Start(async() => { // Wait for prerequisites await Task.WhenAll(buildStep.PrerequisiteSteps.Select(x => x.ExecutedAsync()).ToArray()); // Check for failed prerequisites var status = ResultStatus.NotProcessed; if (buildStep.ArePrerequisitesSuccessful) { try { var outputObjectsGroups = executeContext.GetOutputObjectsGroups(); MicrothreadLocalDatabases.MountDatabase(outputObjectsGroups); // Execute status = await buildStep.Execute(executeContext, builderContext); } catch (TaskCanceledException e) { // Benlitz: I'm NOT SURE this is the correct explanation, it might be a more subtle race condition, but I can't manage to reproduce it again 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."); executeContext.Logger.Warning(e.Message); status = ResultStatus.Cancelled; } catch (Exception e) { executeContext.Logger.Error("Exception in command " + buildStep + ": " + e); status = ResultStatus.Failed; } finally { MicrothreadLocalDatabases.UnmountDatabase(); // Ensure the command set at least the result status if (status == ResultStatus.NotProcessed) { throw new InvalidDataException("The build step " + buildStep + " returned ResultStatus.NotProcessed after completion."); } } if (microThread.Exception != null) { executeContext.Logger.Error("Exception in command " + buildStep + ": " + microThread.Exception); status = ResultStatus.Failed; } } else { status = ResultStatus.NotTriggeredPrerequisiteFailed; } //if (completedTask.IsCanceled) //{ // completedStep.Status = ResultStatus.Cancelled; //} var logType = LogMessageType.Info; string logText = null; switch (status) { case ResultStatus.Successful: logType = LogMessageType.Verbose; logText = "BuildStep {0} was successful.".ToFormat(buildStep.ToString()); break; case ResultStatus.Failed: logType = LogMessageType.Error; logText = "BuildStep {0} failed.".ToFormat(buildStep.ToString()); break; case ResultStatus.NotTriggeredPrerequisiteFailed: logType = LogMessageType.Error; logText = "BuildStep {0} failed of previous failed prerequisites.".ToFormat(buildStep.ToString()); break; case ResultStatus.Cancelled: logType = LogMessageType.Warning; logText = "BuildStep {0} cancelled.".ToFormat(buildStep.ToString()); break; case ResultStatus.NotTriggeredWasSuccessful: logType = LogMessageType.Verbose; logText = "BuildStep {0} is up-to-date and has been skipped".ToFormat(buildStep.ToString()); break; case ResultStatus.NotProcessed: throw new InvalidDataException("BuildStep has neither succeeded, failed, nor been cancelled"); } if (logText != null) { var logMessage = new LogMessage(null, logType, logText); executeContext.Logger.Log(logMessage); } buildStep.RegisterResult(executeContext, status); stepCounter.AddStepResult(status); }); } else { buildStep.Clean(executeContext, builderContext, runMode == Mode.CleanAndDelete); } } }