/// <summary> /// Walks through the set of tasks for this target and processes them by handing them off to the TaskBuilder. /// </summary> /// <returns> /// The result of the tasks, based on the last task which ran. /// </returns> private async Task <WorkUnitResult> ProcessBucket(ITaskBuilder taskBuilder, TargetLoggingContext targetLoggingContext, TaskExecutionMode mode, Lookup lookupForInference, Lookup lookupForExecution) { WorkUnitResultCode aggregatedTaskResult = WorkUnitResultCode.Success; WorkUnitActionCode finalActionCode = WorkUnitActionCode.Continue; WorkUnitResult lastResult = new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null); try { // Grab the task builder so if cancel is called it will have something to operate on. _currentTaskBuilder = taskBuilder; int currentTask = 0; // Walk through all of the tasks and execute them in order. for (; (currentTask < _target.Children.Count) && !_cancellationToken.IsCancellationRequested; ++currentTask) { ProjectTargetInstanceChild targetChildInstance = _target.Children[currentTask]; if (DebuggerManager.DebuggingEnabled) { DebuggerManager.EnterState(targetChildInstance.Location, lookupForExecution.GlobalsForDebugging /* does not matter which lookup we get this from */); } // Execute the task. lastResult = await taskBuilder.ExecuteTask(targetLoggingContext, _requestEntry, _targetBuilderCallback, targetChildInstance, mode, lookupForInference, lookupForExecution, _cancellationToken); if (DebuggerManager.DebuggingEnabled) { DebuggerManager.LeaveState(targetChildInstance.Location); } if (lastResult.ResultCode == WorkUnitResultCode.Failed) { aggregatedTaskResult = WorkUnitResultCode.Failed; } else if (lastResult.ResultCode == WorkUnitResultCode.Success && aggregatedTaskResult != WorkUnitResultCode.Failed) { aggregatedTaskResult = WorkUnitResultCode.Success; } if (lastResult.ActionCode == WorkUnitActionCode.Stop) { finalActionCode = WorkUnitActionCode.Stop; break; } } if (_cancellationToken.IsCancellationRequested) { aggregatedTaskResult = WorkUnitResultCode.Canceled; finalActionCode = WorkUnitActionCode.Stop; } } finally { _currentTaskBuilder = null; } return(new WorkUnitResult(aggregatedTaskResult, finalActionCode, lastResult.Exception)); }
/// <summary> /// Aggregates the specified result with this result and returns the aggregation. /// </summary> /// <remarks> /// The rules are: /// 1. Errors take precedence over success. /// 2. Success takes precedence over skipped. /// 3. Stop takes precedence over continue. /// 4. The first exception in the result wins. /// </remarks> internal WorkUnitResult AggregateResult(WorkUnitResult result) { WorkUnitResultCode aggregateResult = _resultCode; WorkUnitActionCode aggregateAction = _actionCode; Exception aggregateException = _exception; if (result._resultCode == WorkUnitResultCode.Canceled || result.ResultCode == WorkUnitResultCode.Failed) { // Failed and canceled take priority aggregateResult = result._resultCode; } else if (result._resultCode == WorkUnitResultCode.Success && aggregateResult == WorkUnitResultCode.Skipped) { // Success only counts if we were previously in the skipped category. aggregateResult = result._resultCode; } if (result._actionCode == WorkUnitActionCode.Stop) { aggregateAction = result.ActionCode; } if (aggregateException == null) { aggregateException = result._exception; } return(new WorkUnitResult(aggregateResult, aggregateAction, aggregateException)); }
/// <summary> /// Initializes the results with specified items and result. /// </summary> /// <param name="items">The items produced by the target.</param> /// <param name="result">The overall result for the target.</param> internal TargetResult(TaskItem[] items, WorkUnitResult result) { ErrorUtilities.VerifyThrowArgumentNull(items, "items"); ErrorUtilities.VerifyThrowArgumentNull(result, "result"); _itemsStore = new ItemsStore(items); _result = result; }
/// <summary> /// Runs all of the tasks for this target, batched as necessary. /// </summary> internal async Task ExecuteTarget(ITaskBuilder taskBuilder, BuildRequestEntry requestEntry, ProjectLoggingContext projectLoggingContext, CancellationToken cancellationToken) { #if MSBUILDENABLEVSPROFILING try { string beginTargetBuild = String.Format(CultureInfo.CurrentCulture, "Build Target {0} in Project {1} - Start", this.Name, projectFullPath); DataCollection.CommentMarkProfile(8800, beginTargetBuild); #endif try { VerifyState(_state, TargetEntryState.Execution); ErrorUtilities.VerifyThrow(!_isExecuting, "Target {0} is already executing", _target.Name); _cancellationToken = cancellationToken; _isExecuting = true; // Generate the batching buckets. Note that each bucket will get a lookup based on the baseLookup. This lookup will be in its // own scope, which we will collapse back down into the baseLookup at the bottom of the function. List <ItemBucket> buckets = BatchingEngine.PrepareBatchingBuckets(GetBatchableParametersForTarget(), _baseLookup, _target.Location); WorkUnitResult aggregateResult = new WorkUnitResult(); TargetLoggingContext targetLoggingContext = null; bool targetSuccess = false; int numberOfBuckets = buckets.Count; string projectFullPath = requestEntry.RequestConfiguration.ProjectFullPath; string parentTargetName = null; if (ParentEntry != null && ParentEntry.Target != null) { parentTargetName = ParentEntry.Target.Name; } for (int i = 0; i < numberOfBuckets; i++) { ItemBucket bucket = buckets[i]; // If one of the buckets failed, stop building. if (aggregateResult.ActionCode == WorkUnitActionCode.Stop) { break; } targetLoggingContext = projectLoggingContext.LogTargetBatchStarted(projectFullPath, _target, parentTargetName); WorkUnitResult bucketResult = null; targetSuccess = false; Lookup.Scope entryForInference = null; Lookup.Scope entryForExecution = null; try { // This isn't really dependency analysis. This is up-to-date checking. Based on this we will be able to determine if we should // run tasks in inference or execution mode (or both) or just skip them altogether. ItemDictionary <ProjectItemInstance> changedTargetInputs; ItemDictionary <ProjectItemInstance> upToDateTargetInputs; Lookup lookupForInference; Lookup lookupForExecution; // UNDONE: (Refactor) Refactor TargetUpToDateChecker to take a logging context, not a logging service. TargetUpToDateChecker dependencyAnalyzer = new TargetUpToDateChecker(requestEntry.RequestConfiguration.Project, _target, targetLoggingContext.LoggingService, targetLoggingContext.BuildEventContext); DependencyAnalysisResult dependencyResult = dependencyAnalyzer.PerformDependencyAnalysis(bucket, out changedTargetInputs, out upToDateTargetInputs); switch (dependencyResult) { // UNDONE: Need to enter/leave debugger scope properly for the <Target> element. case DependencyAnalysisResult.FullBuild: case DependencyAnalysisResult.IncrementalBuild: case DependencyAnalysisResult.SkipUpToDate: // Create the lookups used to hold the current set of properties and items lookupForInference = bucket.Lookup; lookupForExecution = bucket.Lookup.Clone(); // Push the lookup stack up one so that we are only modifying items and properties in that scope. entryForInference = lookupForInference.EnterScope("ExecuteTarget() Inference"); entryForExecution = lookupForExecution.EnterScope("ExecuteTarget() Execution"); // if we're doing an incremental build, we need to effectively run the task twice -- once // to infer the outputs for up-to-date input items, and once to actually execute the task; // as a result we need separate sets of item and property collections to track changes if (dependencyResult == DependencyAnalysisResult.IncrementalBuild) { // subset the relevant items to those that are up-to-date foreach (string itemType in upToDateTargetInputs.ItemTypes) { lookupForInference.PopulateWithItems(itemType, upToDateTargetInputs[itemType]); } // subset the relevant items to those that have changed foreach (string itemType in changedTargetInputs.ItemTypes) { lookupForExecution.PopulateWithItems(itemType, changedTargetInputs[itemType]); } } // We either have some work to do or at least we need to infer outputs from inputs. bucketResult = await ProcessBucket(taskBuilder, targetLoggingContext, GetTaskExecutionMode(dependencyResult), lookupForInference, lookupForExecution); // Now aggregate the result with the existing known results. There are four rules, assuming the target was not // skipped due to being up-to-date: // 1. If this bucket failed or was cancelled, the aggregate result is failure. // 2. If this bucket Succeeded and we have not previously failed, the aggregate result is a success. // 3. Otherwise, the bucket was skipped, which has no effect on the aggregate result. // 4. If the bucket's action code says to stop, then we stop, regardless of the success or failure state. if (dependencyResult != DependencyAnalysisResult.SkipUpToDate) { aggregateResult = aggregateResult.AggregateResult(bucketResult); } else { if (aggregateResult.ResultCode == WorkUnitResultCode.Skipped) { aggregateResult = aggregateResult.AggregateResult(new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null)); } } // Pop the lookup scopes, causing them to collapse their values back down into the // bucket's lookup. // NOTE: this order is important because when we infer outputs, we are trying // to produce the same results as would be produced from a full build; as such // if we're doing both the infer and execute steps, we want the outputs from // the execute step to override the outputs of the infer step -- this models // the full build scenario more correctly than if the steps were reversed entryForInference.LeaveScope(); entryForInference = null; entryForExecution.LeaveScope(); entryForExecution = null; targetSuccess = (bucketResult != null) && (bucketResult.ResultCode == WorkUnitResultCode.Success); break; case DependencyAnalysisResult.SkipNoInputs: case DependencyAnalysisResult.SkipNoOutputs: // We have either no inputs or no outputs, so there is nothing to do. targetSuccess = true; break; } } catch (InvalidProjectFileException e) { // Make sure the Invalid Project error gets logged *before* TargetFinished. Otherwise, // the log is confusing. targetLoggingContext.LogInvalidProjectFileError(e); if (null != entryForInference) { entryForInference.LeaveScope(); } if (null != entryForExecution) { entryForExecution.LeaveScope(); } aggregateResult = aggregateResult.AggregateResult(new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null)); } finally { // Don't log the last target finished event until we can process the target outputs as we want to attach them to the // last target batch. if (targetLoggingContext != null && i < numberOfBuckets - 1) { targetLoggingContext.LogTargetBatchFinished(projectFullPath, targetSuccess, null); targetLoggingContext = null; } } } // Produce the final results. List <TaskItem> targetOutputItems = new List <TaskItem>(); try { // If any legacy CallTarget operations took place, integrate them back in to the main lookup now. LeaveLegacyCallTargetScopes(); // Publish the items for each bucket back into the baseLookup. Note that EnterScope() was actually called on each // bucket inside of the ItemBucket constructor, which is why you don't see it anywhere around here. foreach (ItemBucket bucket in buckets) { bucket.LeaveScope(); } string targetReturns = _target.Returns; ElementLocation targetReturnsLocation = _target.ReturnsLocation; // If there are no targets in the project file that use the "Returns" attribute, that means that we // revert to the legacy behavior in the case where Returns is not specified (null, rather // than the empty string, which indicates no returns). Legacy behavior is for all // of the target's Outputs to be returned. // On the other hand, if there is at least one target in the file that uses the Returns attribute, // then all targets in the file are run according to the new behaviour (return nothing unless otherwise // specified by the Returns attribute). if (targetReturns == null) { if (!_target.ParentProjectSupportsReturnsAttribute) { targetReturns = _target.Outputs; targetReturnsLocation = _target.OutputsLocation; } } if (!String.IsNullOrEmpty(targetReturns)) { // Determine if we should keep duplicates. bool keepDupes = ConditionEvaluator.EvaluateCondition ( _target.KeepDuplicateOutputs, ParserOptions.AllowPropertiesAndItemLists, _expander, ExpanderOptions.ExpandPropertiesAndItems, requestEntry.ProjectRootDirectory, _target.KeepDuplicateOutputsLocation, projectLoggingContext.LoggingService, projectLoggingContext.BuildEventContext ); // NOTE: we need to gather the outputs in batches, because the output specification may reference item metadata // Also, we are using the baseLookup, which has possibly had changes made to it since the project started. Because of this, the // set of outputs calculated here may differ from those which would have been calculated at the beginning of the target. It is // assumed the user intended this. List <ItemBucket> batchingBuckets = BatchingEngine.PrepareBatchingBuckets(GetBatchableParametersForTarget(), _baseLookup, _target.Location); if (keepDupes) { foreach (ItemBucket bucket in batchingBuckets) { targetOutputItems.AddRange(bucket.Expander.ExpandIntoTaskItemsLeaveEscaped(targetReturns, ExpanderOptions.ExpandAll, targetReturnsLocation)); } } else { HashSet <TaskItem> addedItems = new HashSet <TaskItem>(); foreach (ItemBucket bucket in batchingBuckets) { IList <TaskItem> itemsToAdd = bucket.Expander.ExpandIntoTaskItemsLeaveEscaped(targetReturns, ExpanderOptions.ExpandAll, targetReturnsLocation); foreach (TaskItem item in itemsToAdd) { if (!addedItems.Contains(item)) { targetOutputItems.Add(item); addedItems.Add(item); } } } } } } finally { if (targetLoggingContext != null) { // log the last target finished since we now have the target outputs. targetLoggingContext.LogTargetBatchFinished(projectFullPath, targetSuccess, targetOutputItems != null && targetOutputItems.Count > 0 ? targetOutputItems : null); } } _targetResult = new TargetResult(targetOutputItems.ToArray(), aggregateResult); if (aggregateResult.ResultCode == WorkUnitResultCode.Failed && aggregateResult.ActionCode == WorkUnitActionCode.Stop) { _state = TargetEntryState.ErrorExecution; } else { _state = TargetEntryState.Completed; } } finally { _isExecuting = false; } #if MSBUILDENABLEVSPROFILING } finally { string endTargetBuild = String.Format(CultureInfo.CurrentCulture, "Build Target {0} in Project {1} - End", this.Name, projectFullPath); DataCollection.CommentMarkProfile(8801, endTargetBuild); } #endif }
/// <summary> /// Creates a build result for a request /// </summary> private BuildResult CreateBuildResult(BuildRequest request, string target, WorkUnitResult workUnitResult) { BuildResult result = new BuildResult(request); result.AddResultsForTarget(target, new TargetResult(new TaskItem[] { }, workUnitResult)); return result; }
/// <summary> /// Creates and caches a built result. /// </summary> private BuildResult CacheBuildResult(BuildRequest request, string target, WorkUnitResult workUnitResult) { BuildResult result = CreateBuildResult(request, target, workUnitResult); IResultsCache resultsCache = _host.GetComponent(BuildComponentType.ResultsCache) as IResultsCache; resultsCache.AddResult(result); return result; }
/// <summary> /// Constructor allows you to set all the data /// </summary> public TaskDefinition(string name, string condition, bool continueOnError, XmlDocument projectXmlDocument, WorkUnitResult expectedResult) { this.name = name; this.condition = condition; this.continueOnError = continueOnError; this.taskElement = projectXmlDocument.CreateElement(this.name, @"http://schemas.microsoft.com/developer/msbuild/2003"); this.parentXmlDocument = projectXmlDocument; this.expectedResult = expectedResult; this.finalTaskParameters = new Dictionary<string, string>(); this.taskExecuted = new AutoResetEvent(false); this.taskStarted = new AutoResetEvent(false); GenerateTaskElement(); }
/// <summary> /// Basic constructor which takes the task name and the task expected status /// </summary> public TaskDefinition(string name, XmlDocument projectXmlDocument, WorkUnitResult expectedResult) : this(name, null, false, projectXmlDocument, expectedResult) { }
/// <summary> /// Aggregates the specified result with this result and returns the aggregation. /// </summary> /// <remarks> /// The rules are: /// 1. Errors take precedence over success. /// 2. Success takes precedence over skipped. /// 3. Stop takes precedence over continue. /// 4. The first exception in the result wins. /// </remarks> internal WorkUnitResult AggregateResult(WorkUnitResult result) { WorkUnitResultCode aggregateResult = _resultCode; WorkUnitActionCode aggregateAction = _actionCode; Exception aggregateException = _exception; if (result._resultCode == WorkUnitResultCode.Canceled || result.ResultCode == WorkUnitResultCode.Failed) { // Failed and canceled take priority aggregateResult = result._resultCode; } else if (result._resultCode == WorkUnitResultCode.Success && aggregateResult == WorkUnitResultCode.Skipped) { // Success only counts if we were previously in the skipped category. aggregateResult = result._resultCode; } if (result._actionCode == WorkUnitActionCode.Stop) { aggregateAction = result.ActionCode; } if (aggregateException == null) { aggregateException = result._exception; } return new WorkUnitResult(aggregateResult, aggregateAction, aggregateException); }
/// <summary> /// Execute a task object for a given bucket. /// </summary> /// <param name="taskExecutionHost">The host used to execute the task.</param> /// <param name="taskLoggingContext">The logging context.</param> /// <param name="taskHost">The task host for the task.</param> /// <param name="bucket">The batching bucket</param> /// <param name="howToExecuteTask">The task execution mode</param> /// <returns>The result of running the task.</returns> private async Task<WorkUnitResult> ExecuteInstantiatedTask(ITaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask) { UpdateContinueOnError(bucket, taskHost); bool taskResult = false; WorkUnitResultCode resultCode = WorkUnitResultCode.Success; WorkUnitActionCode actionCode = WorkUnitActionCode.Continue; if (!taskExecutionHost.SetTaskParameters(_taskNode.ParametersForBuild)) { // The task cannot be initialized. ProjectErrorUtilities.VerifyThrowInvalidProject(false, _targetChildInstance.Location, "TaskParametersError", _taskNode.Name, String.Empty); } else { bool taskReturned = false; Exception taskException = null; // If this is the MSBuild task, we need to execute it's special internal method. TaskExecutionHost host = taskExecutionHost as TaskExecutionHost; Type taskType = host.TaskInstance.GetType(); try { if (taskType == typeof(MSBuild)) { MSBuild msbuildTask = host.TaskInstance as MSBuild; ErrorUtilities.VerifyThrow(msbuildTask != null, "Unexpected MSBuild internal task."); _targetBuilderCallback.EnterMSBuildCallbackState(); try { taskResult = await msbuildTask.ExecuteInternal(); } finally { _targetBuilderCallback.ExitMSBuildCallbackState(); } } else if (taskType == typeof(CallTarget)) { CallTarget callTargetTask = host.TaskInstance as CallTarget; taskResult = await callTargetTask.ExecuteInternal(); } else { using (FullTracking.Track(taskLoggingContext.TargetLoggingContext.Target.Name, _taskNode.Name, _buildRequestEntry.ProjectRootDirectory, _buildRequestEntry.RequestConfiguration.Project.PropertiesToBuildWith)) { taskResult = taskExecutionHost.Execute(); } } } catch (Exception ex) { if (ExceptionHandling.IsCriticalException(ex) || (Environment.GetEnvironmentVariable("MSBUILDDONOTCATCHTASKEXCEPTIONS") == "1")) { throw; } taskException = ex; } if (taskException == null) { taskReturned = true; // Set the property "MSBuildLastTaskResult" to reflect whether the task succeeded or not. // The main use of this is if ContinueOnError is true -- so that the next task can consult the result. // So we want it to be "false" even if ContinueOnError is true. // The constants "true" and "false" should NOT be localized. They become property values. bucket.Lookup.SetProperty(ProjectPropertyInstance.Create(ReservedPropertyNames.lastTaskResult, taskResult ? "true" : "false", true/* may be reserved */, _buildRequestEntry.RequestConfiguration.Project.IsImmutable)); } else { Type type = taskException.GetType(); if (type == typeof(LoggerException)) { // if a logger has failed, abort immediately // Polite logger failure _continueOnError = ContinueOnError.ErrorAndStop; // Rethrow wrapped in order to avoid losing the callstack throw new LoggerException(taskException.Message, taskException); } else if (type == typeof(InternalLoggerException)) { // Logger threw arbitrary exception _continueOnError = ContinueOnError.ErrorAndStop; InternalLoggerException ex = taskException as InternalLoggerException; // Rethrow wrapped in order to avoid losing the callstack throw new InternalLoggerException(taskException.Message, taskException, ex.BuildEventArgs, ex.ErrorCode, ex.HelpKeyword, ex.InitializationException); } else if (type == typeof(ThreadAbortException)) { Thread.ResetAbort(); _continueOnError = ContinueOnError.ErrorAndStop; // Cannot rethrow wrapped as ThreadAbortException is sealed and has no appropriate constructor // Stack will be lost throw taskException; } else if (type == typeof(BuildAbortedException)) { _continueOnError = ContinueOnError.ErrorAndStop; // Rethrow wrapped in order to avoid losing the callstack throw new BuildAbortedException(taskException.Message, ((BuildAbortedException)taskException)); } else if (type == typeof(CircularDependencyException)) { _continueOnError = ContinueOnError.ErrorAndStop; ProjectErrorUtilities.ThrowInvalidProject(taskLoggingContext.Task.Location, "CircularDependency", taskLoggingContext.TargetLoggingContext.Target.Name); } else if (type == typeof(InvalidProjectFileException)) { // Just in case this came out of a task, make sure it's not // marked as having been logged. InvalidProjectFileException ipex = (InvalidProjectFileException)taskException; ipex.HasBeenLogged = false; if (_continueOnError != ContinueOnError.ErrorAndStop) { taskLoggingContext.LogInvalidProjectFileError(ipex); taskLoggingContext.LogComment(MessageImportance.Normal, "ErrorConvertedIntoWarning"); } else { // Rethrow wrapped in order to avoid losing the callstack throw new InvalidProjectFileException(ipex.Message, ipex); } } else if (type == typeof(Exception) || type.IsSubclassOf(typeof(Exception))) { // Occasionally, when debugging a very uncommon task exception, it is useful to loop the build with // a debugger attached to break on 2nd chance exceptions. // That requires that there needs to be a way to not catch here, by setting an environment variable. if (ExceptionHandling.IsCriticalException(taskException) || (Environment.GetEnvironmentVariable("MSBUILDDONOTCATCHTASKEXCEPTIONS") == "1")) { // Wrapping in an Exception will unfortunately mean that this exception would fly through any IsCriticalException above. // However, we should not have any, also we should not have stashed such an exception anyway. throw new Exception(taskException.Message, taskException); } Exception exceptionToLog = taskException; if (exceptionToLog is TargetInvocationException) { exceptionToLog = exceptionToLog.InnerException; } // handle any exception thrown by the task during execution // NOTE: We catch ALL exceptions here, to attempt to completely isolate the Engine // from failures in the task. if (_continueOnError == ContinueOnError.WarnAndContinue) { taskLoggingContext.LogTaskWarningFromException ( new BuildEventFileInfo(_targetChildInstance.Location), exceptionToLog, _taskNode.Name ); // Log a message explaining why we converted the previous error into a warning. taskLoggingContext.LogComment(MessageImportance.Normal, "ErrorConvertedIntoWarning"); } else { taskLoggingContext.LogFatalTaskError ( new BuildEventFileInfo(_targetChildInstance.Location), exceptionToLog, _taskNode.Name ); } } else { ErrorUtilities.ThrowInternalErrorUnreachable(); } } // If the task returned attempt to gather its outputs. If gathering outputs fails set the taskResults // to false if (taskReturned) { taskResult = GatherTaskOutputs(taskExecutionHost, howToExecuteTask, bucket) && taskResult; } // If the taskResults are false look at ContinueOnError. If ContinueOnError=false (default) // mark the taskExecutedSuccessfully=false. Otherwise let the task succeed but log a normal // pri message that says this task is continuing because ContinueOnError=true resultCode = taskResult ? WorkUnitResultCode.Success : WorkUnitResultCode.Failed; actionCode = WorkUnitActionCode.Continue; if (resultCode == WorkUnitResultCode.Failed) { if (_continueOnError == ContinueOnError.ErrorAndStop) { actionCode = WorkUnitActionCode.Stop; } else { // This is the ErrorAndContinue or WarnAndContinue case... string settingString = "true"; if (_taskNode.ContinueOnErrorLocation != null) { settingString = bucket.Expander.ExpandIntoStringAndUnescape(_taskNode.ContinueOnError, ExpanderOptions.ExpandAll, _taskNode.ContinueOnErrorLocation); // expand embedded item vectors after expanding properties and item metadata } taskLoggingContext.LogComment ( MessageImportance.Normal, "TaskContinuedDueToContinueOnError", "ContinueOnError", _taskNode.Name, settingString ); actionCode = WorkUnitActionCode.Continue; } } } WorkUnitResult result = new WorkUnitResult(resultCode, actionCode, null); return result; }
private WorkUnitResult ExecuteTaskInSTAThread(ItemBucket bucket, TaskLoggingContext taskLoggingContext, IDictionary<string, string> taskIdentityParameters, TaskHost taskHost, TaskExecutionMode howToExecuteTask) { WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null); Thread staThread = null; Exception exceptionFromExecution = null; ManualResetEvent taskRunnerFinished = new ManualResetEvent(false); try { ThreadStart taskRunnerDelegate = delegate () { Lookup.Scope scope = bucket.Lookup.EnterScope("STA Thread for Task"); try { taskResult = InitializeAndExecuteTask(taskLoggingContext, bucket, taskIdentityParameters, taskHost, howToExecuteTask).Result; } catch (Exception e) { if (ExceptionHandling.IsCriticalException(e)) { throw; } exceptionFromExecution = e; } finally { scope.LeaveScope(); taskRunnerFinished.Set(); } }; staThread = new Thread(taskRunnerDelegate); staThread.SetApartmentState(ApartmentState.STA); staThread.Name = "MSBuild STA task runner thread"; staThread.CurrentCulture = _componentHost.BuildParameters.Culture; staThread.CurrentUICulture = _componentHost.BuildParameters.UICulture; staThread.Start(); // TODO: Why not just Join on the thread??? taskRunnerFinished.WaitOne(); } finally { taskRunnerFinished.Close(); taskRunnerFinished = null; } if (exceptionFromExecution != null) { // Unfortunately this will reset the callstack throw exceptionFromExecution; } return taskResult; }
/// <summary> /// Execute a single bucket /// </summary> /// <returns>true if execution succeeded</returns> private async Task<WorkUnitResult> ExecuteBucket(TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask, Dictionary<string, string> lookupHash) { // On Intrinsic tasks, we do not allow batchable params, therefore metadata is excluded. ParserOptions parserOptions = (_taskNode == null) ? ParserOptions.AllowPropertiesAndItemLists : ParserOptions.AllowAll; WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null); bool condition = ConditionEvaluator.EvaluateCondition ( _targetChildInstance.Condition, parserOptions, bucket.Expander, ExpanderOptions.ExpandAll, _buildRequestEntry.ProjectRootDirectory, _targetChildInstance.ConditionLocation, _targetLoggingContext.LoggingService, _targetLoggingContext.BuildEventContext ); if (!condition) { LogSkippedTask(bucket, howToExecuteTask); taskResult = new WorkUnitResult(WorkUnitResultCode.Skipped, WorkUnitActionCode.Continue, null); return taskResult; } // If this is an Intrinsic task, it gets handled in a special fashion. if (_taskNode == null) { ExecuteIntrinsicTask(bucket); taskResult = new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null); } else { if (_componentHost.BuildParameters.SaveOperatingEnvironment) { // Change to the project root directory. // If that directory does not exist, do nothing. (Do not check first as it is almost always there and it is slow) // This is because if the project has not been saved, this directory may not exist, yet it is often useful to still be able to build the project. // No errors are masked by doing this: errors loading the project from disk are reported at load time, if necessary. NativeMethodsShared.SetCurrentDirectory(_buildRequestEntry.ProjectRootDirectory); } if (howToExecuteTask == TaskExecutionMode.ExecuteTaskAndGatherOutputs) { // We need to find the task before logging the task started event so that the using task statement comes before the task started event IDictionary<string, string> taskIdentityParameters = GatherTaskIdentityParameters(bucket.Expander); TaskRequirements? requirements = _taskExecutionHost.FindTask(taskIdentityParameters); if (requirements != null) { TaskLoggingContext taskLoggingContext = _targetLoggingContext.LogTaskBatchStarted(_projectFullPath, _targetChildInstance); try { if ( ((requirements.Value & TaskRequirements.RequireSTAThread) == TaskRequirements.RequireSTAThread) && (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) ) { taskResult = ExecuteTaskInSTAThread(bucket, taskLoggingContext, taskIdentityParameters, taskHost, howToExecuteTask); } else { taskResult = await InitializeAndExecuteTask(taskLoggingContext, bucket, taskIdentityParameters, taskHost, howToExecuteTask); } if (lookupHash != null) { List<string> overrideMessages = bucket.Lookup.GetPropertyOverrideMessages(lookupHash); if (overrideMessages != null) { foreach (string s in overrideMessages) { taskLoggingContext.LogCommentFromText(MessageImportance.Low, s); } } } } catch (InvalidProjectFileException e) { // Make sure the Invalid Project error gets logged *before* TaskFinished. Otherwise, // the log is confusing. taskLoggingContext.LogInvalidProjectFileError(e); _continueOnError = ContinueOnError.ErrorAndStop; } finally { // Flag the completion of the task. taskLoggingContext.LogTaskBatchFinished(_projectFullPath, taskResult.ResultCode == WorkUnitResultCode.Success || taskResult.ResultCode == WorkUnitResultCode.Skipped); if (taskResult.ResultCode == WorkUnitResultCode.Failed && _continueOnError == ContinueOnError.WarnAndContinue) { // We coerce the failing result to a successful result. taskResult = new WorkUnitResult(WorkUnitResultCode.Success, taskResult.ActionCode, taskResult.Exception); } } } } else { ErrorUtilities.VerifyThrow(howToExecuteTask == TaskExecutionMode.InferOutputsOnly, "should be inferring"); ErrorUtilities.VerifyThrow ( GatherTaskOutputs(null, howToExecuteTask, bucket), "The method GatherTaskOutputs() should never fail when inferring task outputs." ); if (lookupHash != null) { List<string> overrideMessages = bucket.Lookup.GetPropertyOverrideMessages(lookupHash); if (overrideMessages != null) { foreach (string s in overrideMessages) { _targetLoggingContext.LogCommentFromText(MessageImportance.Low, s); } } } taskResult = new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null); } } return taskResult; }
/// <summary> /// Called to execute a task within a target. This method instantiates the task, sets its parameters, and executes it. /// </summary> /// <returns>true, if successful</returns> private async Task<WorkUnitResult> ExecuteTask(TaskExecutionMode mode, Lookup lookup) { ErrorUtilities.VerifyThrowArgumentNull(lookup, "lookup"); WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null); TaskHost taskHost = null; List<ItemBucket> buckets = null; try { if (_taskNode != null) { taskHost = new TaskHost(_componentHost, _buildRequestEntry, _targetChildInstance.Location, _targetBuilderCallback); _taskExecutionHost.InitializeForTask(taskHost, _targetLoggingContext, _buildRequestEntry.RequestConfiguration.Project, _taskNode.Name, _taskNode.Location, _taskHostObject, _continueOnError != ContinueOnError.ErrorAndStop, taskHost.AppDomainSetup, taskHost.IsOutOfProc, _cancellationToken); } List<string> taskParameterValues = CreateListOfParameterValues(); buckets = BatchingEngine.PrepareBatchingBuckets(taskParameterValues, lookup, _targetChildInstance.Location); Dictionary<string, string> lookupHash = null; // Only create a hash table if there are more than one bucket as this is the only time a property can be overridden if (buckets.Count > 1) { lookupHash = new Dictionary<string, string>(MSBuildNameIgnoreCaseComparer.Default); } WorkUnitResult aggregateResult = new WorkUnitResult(); // Loop through each of the batch buckets and execute them one at a time for (int i = 0; i < buckets.Count; i++) { // Execute the batch bucket, pass in which bucket we are executing so that we know when to get a new taskId for the bucket. taskResult = await ExecuteBucket(taskHost, (ItemBucket)buckets[i], mode, lookupHash); aggregateResult = aggregateResult.AggregateResult(taskResult); if (aggregateResult.ActionCode == WorkUnitActionCode.Stop) { break; } } taskResult = aggregateResult; } finally { _taskExecutionHost.CleanupForTask(); if (taskHost != null) { taskHost.MarkAsInactive(); } // Now all task batches are done, apply all item adds to the outer // target batch; we do this even if the task wasn't found (in that case, // no items or properties will have been added to the scope) if (buckets != null) { foreach (ItemBucket bucket in buckets) { bucket.LeaveScope(); } } } return taskResult; }
/// <summary> /// Builds the task specified by the XML. /// </summary> /// <param name="loggingContext">The logging context of the target</param> /// <param name="requestEntry">The build request entry being built</param> /// <param name="targetBuilderCallback">The target builder callback.</param> /// <param name="taskInstance">The task instance.</param> /// <param name="mode">The mode in which to execute tasks.</param> /// <param name="inferLookup">The lookup to be used for inference.</param> /// <param name="executeLookup">The lookup to be used during execution.</param> /// <returns>The result of running the task batch.</returns> /// <remarks> /// The ExecuteTask method takes a task as specified by XML and executes it. This procedure is comprised /// of the following steps: /// 1. Loading the Task from its containing assembly by looking it up in the task registry /// 2. Determining if the task is batched. If it is, create the batches and execute each as if it were a non-batched task /// 3. If the task is not batched, execute it. /// 4. If the task was batched, hold on to its Lookup until all of the natches are done, then merge them. /// </remarks> public async Task<WorkUnitResult> ExecuteTask(TargetLoggingContext loggingContext, BuildRequestEntry requestEntry, ITargetBuilderCallback targetBuilderCallback, ProjectTargetInstanceChild taskInstance, TaskExecutionMode mode, Lookup inferLookup, Lookup executeLookup, CancellationToken cancellationToken) { ErrorUtilities.VerifyThrow(taskInstance != null, "Need to specify the task instance."); _buildRequestEntry = requestEntry; _targetBuilderCallback = targetBuilderCallback; _cancellationToken = cancellationToken; _targetChildInstance = taskInstance; // In the case of Intrinsic tasks, taskNode will end up null. Currently this is how we distinguish // intrinsic from extrinsic tasks. _taskNode = taskInstance as ProjectTaskInstance; if (_taskNode != null && requestEntry.Request.HostServices != null) { _taskHostObject = requestEntry.Request.HostServices.GetHostObject(requestEntry.RequestConfiguration.Project.FullPath, loggingContext.Target.Name, _taskNode.Name); } _projectFullPath = requestEntry.RequestConfiguration.Project.FullPath; // this.handleId = handleId; No handles // this.parentModule = parentModule; No task execution module _continueOnError = ContinueOnError.ErrorAndStop; _targetLoggingContext = loggingContext; WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null); if ((mode & TaskExecutionMode.InferOutputsOnly) == TaskExecutionMode.InferOutputsOnly) { taskResult = await ExecuteTask(TaskExecutionMode.InferOutputsOnly, inferLookup); } if ((mode & TaskExecutionMode.ExecuteTaskAndGatherOutputs) == TaskExecutionMode.ExecuteTaskAndGatherOutputs) { taskResult = await ExecuteTask(TaskExecutionMode.ExecuteTaskAndGatherOutputs, executeLookup); } return taskResult; }
/// <summary> /// Walks through the set of tasks for this target and processes them by handing them off to the TaskBuilder. /// </summary> /// <returns> /// The result of the tasks, based on the last task which ran. /// </returns> private async Task <WorkUnitResult> ProcessBucket(ITaskBuilder taskBuilder, TargetLoggingContext targetLoggingContext, TaskExecutionMode mode, Lookup lookupForInference, Lookup lookupForExecution) { WorkUnitResultCode aggregatedTaskResult = WorkUnitResultCode.Success; WorkUnitActionCode finalActionCode = WorkUnitActionCode.Continue; WorkUnitResult lastResult = new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null); List <StaticTarget.Task> staticTasks = new List <StaticTarget.Task>(); try { // Grab the task builder so if cancel is called it will have something to operate on. _currentTaskBuilder = taskBuilder; int currentTask = 0; // Walk through all of the tasks and execute them in order. for (; (currentTask < _target.Children.Count) && !_cancellationToken.IsCancellationRequested; ++currentTask) { ProjectTargetInstanceChild targetChildInstance = _target.Children[currentTask]; // Execute the task. lastResult = await taskBuilder.ExecuteTask(targetLoggingContext, _requestEntry, _targetBuilderCallback, targetChildInstance, mode, lookupForInference, lookupForExecution, _cancellationToken, staticTasks); if (lastResult.ResultCode == WorkUnitResultCode.Failed) { aggregatedTaskResult = WorkUnitResultCode.Failed; } else if (lastResult.ResultCode == WorkUnitResultCode.Success && aggregatedTaskResult != WorkUnitResultCode.Failed) { aggregatedTaskResult = WorkUnitResultCode.Success; } if (lastResult.ActionCode == WorkUnitActionCode.Stop) { finalActionCode = WorkUnitActionCode.Stop; break; } } if (_cancellationToken.IsCancellationRequested) { aggregatedTaskResult = WorkUnitResultCode.Canceled; finalActionCode = WorkUnitActionCode.Stop; } } finally { _currentTaskBuilder = null; } StaticTarget staticTarget = null; if (staticTasks.Count > 0) { staticTarget = new StaticTarget() { Location = _target.Location, Tasks = staticTasks, Name = Name }; } return(new WorkUnitResult(aggregatedTaskResult, finalActionCode, lastResult.Exception) { GeneratedStaticTarget = staticTarget, }); }