public void ExecutionProperties() { Engine engine = new Engine(); engine.BinPath="TestBinPath"; ArrayList targetsToBuild = new ArrayList(); targetsToBuild.Add("targetName"); ProjectBuildState projectContext = new ProjectBuildState(new BuildRequest(-1, null, null, (BuildPropertyGroup)null, null, -1, false, false), targetsToBuild, new BuildEventContext(0, 1, 1, 1)); TaskExecutionContext context = new TaskExecutionContext(null, null, null, projectContext, 4, EngineCallback.inProcNode, new BuildEventContext(BuildEventContext.InvalidNodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId)); Assert.IsTrue(context.BuildContext.TargetNamesToBuild.Contains("targetName"), "Expected target list to contain targetName"); Assert.AreEqual(EngineCallback.inProcNode,context.NodeIndex); Assert.IsNull(context.ParentTarget,"Expected ParentTarget to be null"); context.SetTaskOutputs(false, null, 0); Assert.IsFalse(context.TaskExecutedSuccessfully ); Assert.IsNull(context.ThrownException, "Expected ThrownException to be null"); context.SetTaskOutputs(true, new Exception(), 0); Assert.IsTrue(context.TaskExecutedSuccessfully); Assert.IsNotNull(context.ThrownException,"Expected ThrownException to not be null"); }
public void TaskExecutionContextCreation() { // Create some items to instantiate a task execution context and check to make sure those values are set properly Engine engine = new Engine(); engine.BinPath="TestBinPath"; ArrayList targetsToBuild = new ArrayList(); targetsToBuild.Add("targetName"); ProjectBuildState projectContext = new ProjectBuildState(null, targetsToBuild, new BuildEventContext(0, 1, 1, 1)); TaskExecutionContext context = new TaskExecutionContext(null, null, null, projectContext, 4, EngineCallback.inProcNode, new BuildEventContext(BuildEventContext.InvalidNodeId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId)); Assert.IsTrue(context.BuildContext.TargetNamesToBuild.Contains("targetName"),"Expected target list to contain targetName"); Assert.IsNull(context.ParentTarget,"ParentTarget should be null"); Assert.IsNull(context.ThrownException,"ThrownException should be null"); Assert.AreEqual(4,context.HandleId,"Node ProxyId should be 4"); }
/// <summary> /// This method is called repeatedly to execute the target in multi-threaded mode. In single /// threaded mode it is called once and it loops internally until the execution is finished. /// </summary> /// <param name="buildContext">Context within which the target is being executed</param> /// <param name="taskExecutionContext">Result of last execution (multi-threaded only)</param> internal void ContinueBuild( ProjectBuildState buildContext, TaskExecutionContext taskExecutionContext) { executionState.ContinueBuild(buildContext, taskExecutionContext); }
/// <summary> /// This method creates a new TaskExecutionContext and return a integer token that maps to it. /// This method is not thread safe and must be called only from the engine thread. /// </summary> internal int CreateTaskContext ( Project parentProject, Target parentTarget, ProjectBuildState buildContext, XmlElement taskNode, int nodeIndex, BuildEventContext taskContext ) { int handleId = nextContextId; nextContextId = nextContextId + 1; TaskExecutionContext executionContext = new TaskExecutionContext(parentProject, parentTarget, taskNode, buildContext, handleId, nodeIndex, taskContext); executionContexts.Add(handleId, executionContext); return handleId; }
internal void ContinueBuild(ProjectBuildState buildContext, TaskExecutionContext taskExecutionContext) { if (Engine.debugMode) { Console.WriteLine("Project continue build :" + buildContext.BuildRequest.ProjectFileName + " Handle " + buildContext.BuildRequest.HandleId + " State " + buildContext.CurrentBuildContextState + " current target " + buildContext.NameOfTargetInProgress + " blocking target " + buildContext.NameOfBlockingTarget); } bool exitedDueToError = true; try { if (buildContext.CurrentBuildContextState == ProjectBuildState.BuildContextState.BuildingCurrentTarget) { // Execute the next appropriate operation for this target ErrorUtilities.VerifyThrow( taskExecutionContext != null, "Task context should be non-null"); taskExecutionContext.ParentTarget.ContinueBuild(taskExecutionContext.BuildContext, taskExecutionContext); } else if (buildContext.CurrentBuildContextState == ProjectBuildState.BuildContextState.StartingFirstTarget) { // Start the first target of the build request buildContext.CurrentBuildContextState = ProjectBuildState.BuildContextState.BuildingCurrentTarget; GetTargetForName(buildContext.NameOfTargetInProgress).Build(buildContext); } else if (buildContext.CurrentBuildContextState == ProjectBuildState.BuildContextState.CycleDetected) { ErrorUtilities.VerifyThrow( taskExecutionContext != null && taskExecutionContext.ParentTarget != null, "Unexpected task context. Should not be null"); // Check that the target is in progress ErrorUtilities.VerifyThrow( taskExecutionContext.ParentTarget.TargetBuildState == Target.BuildState.InProgress, "The target forming the cycle should not be complete"); // Throw invalid project exeception ProjectErrorUtilities.VerifyThrowInvalidProject (false, taskExecutionContext.ParentTarget.TargetElement, "CircularDependency", taskExecutionContext.ParentTarget.Name); } CalculateNextActionForProjectContext(buildContext); exitedDueToError = false; if (Engine.debugMode) { Console.WriteLine("Project after continue build :" + buildContext.BuildRequest.ProjectFileName + " Handle " + buildContext.BuildRequest.HandleId + " State " + buildContext.CurrentBuildContextState + " current target " + buildContext.NameOfTargetInProgress + " blocking target " + buildContext.NameOfBlockingTarget); } } catch (InvalidProjectFileException e) { // Make sure the Invalid Project error gets logged *before* ProjectFinished. Otherwise, // the log is confusing. this.ParentEngine.LoggingServices.LogInvalidProjectFileError(buildContext.ProjectBuildEventContext, e); } finally { if ( (exitedDueToError || buildContext.BuildComplete) && buildContext.CurrentBuildContextState != ProjectBuildState.BuildContextState.RequestFilled) { // If the target that threw an exception is being built due to an // dependson or onerror relationship, it is necessary to make sure // the buildrequests waiting on targets below it get notified of the failure. In single // threaded mode there is only a single outstanding request so this issue is avoided. if (exitedDueToError) { buildContext.RecordBuildException(); if (buildContext.NameOfBlockingTarget != null) { while (buildContext.NameOfBlockingTarget != null) { Target blockingTarget = GetTargetForName(buildContext.NameOfBlockingTarget); if (blockingTarget.ExecutionState != null && blockingTarget.ExecutionState.BuildingRequiredTargets) { blockingTarget.ContinueBuild(buildContext, null); } buildContext.RemoveBlockingTarget(); } Target inprogressTarget = GetTargetForName(buildContext.NameOfTargetInProgress); if (inprogressTarget.ExecutionState != null && inprogressTarget.ExecutionState.BuildingRequiredTargets) { inprogressTarget.ContinueBuild(buildContext, null); } } buildContext.CurrentBuildContextState = ProjectBuildState.BuildContextState.BuildComplete; } this.buildingCount--; if (buildContext.BuildRequest.FireProjectStartedFinishedEvents) { ParentEngine.LoggingServices.LogProjectFinished(buildContext.ProjectBuildEventContext, FullFileName, buildContext.BuildResult); } // Notify targets in other projects that are waiting on us via IBuildEngine // interface (via MSBuild and CallTarget tasks). if (buildContext.BuildRequest.IsGeneratedRequest) { if (Engine.debugMode) { Console.WriteLine("Notifying about " + buildContext.BuildRequest.ProjectFileName + " about " + buildContext.TargetNamesToBuild[0] + " on node " + buildContext.BuildRequest.NodeIndex + " HandleId " + buildContext.BuildRequest.HandleId + " ReqID " + buildContext.BuildRequest.RequestId); } ParentEngine.Router.PostDoneNotice(buildContext.BuildRequest); } // Don't try to unload projects loaded by the host if (this.buildingCount == 0 && this.needToUnloadProject && !this.IsLoadedByHost) { parentEngine.UnloadProject(this, false /* unload only this project version */); } buildContext.CurrentBuildContextState = ProjectBuildState.BuildContextState.RequestFilled; } } }
/// <summary> /// This function is called to break the link between two targets that creates a cycle. The link could be /// due to depends/onerror relationship between parent and child. In that case both parent and child are /// on the same node and within the same project. Or the link could be formed by an IBuildEngine callback /// (made such by tasks such as MSBuild or CallTarget) in which case there maybe multiple requests forming /// same link between parent and child. Also in that case parent and child maybe on different nodes and/or in /// different projects. In either case the break is forced by finding the correct builds states and causing /// them to fail. /// </summary> internal void BreakCycle(TargetInProgessState child, TargetInProgessState parent) { ErrorUtilities.VerifyThrow( child.TargetId.nodeId == parentEngine.NodeId, "Expect the child target to be on the node"); Project parentProject = projectManager.GetProject(child.TargetId.projectId); ErrorUtilities.VerifyThrow(parentProject != null, "Expect the parent project to be on the node"); Target childTarget = parentProject.Targets[child.TargetId.name]; List<ProjectBuildState> parentStates = FindConnectingContexts(child, parent, childTarget, childTarget.ExecutionState.GetWaitingBuildContexts(), childTarget.ExecutionState.InitiatingBuildContext); ErrorUtilities.VerifyThrow(parentStates.Count > 0, "Must find at least one matching context"); for (int i = 0; i < parentStates.Count; i++) { parentStates[i].CurrentBuildContextState = ProjectBuildState.BuildContextState.CycleDetected; TaskExecutionContext taskExecutionContext = new TaskExecutionContext(parentProject, childTarget, null, parentStates[i], EngineCallback.invalidEngineHandle, EngineCallback.inProcNode, null); parentEngine.PostTaskOutputUpdates(taskExecutionContext); } }
/// <summary> /// Iterate over the contexts waiting for the target - triggering updates for each of them since the target /// is complete /// </summary> internal void NotifyWaitingTargets(ProjectBuildState errorContext) { // If there was a failure (either unhandled exception or a cycle) the stack will // not unwind properly (i.e. via ContinueBuild call). Therefore the initiating request // must be notified the target completed if the error occurred in another context if (errorContext != null) { AddWaitingBuildContext(initiatingBuildContext); } // Notify the target within the same project that are waiting for current target // These targets are in the process of either building dependencies or error targets // or part of a sequential build context while (waitingTargets != null && waitingTargets.Count != 0) { //Grab the first context ProjectBuildState buildContext = waitingTargets[0]; waitingTargets.RemoveAt(0); //Don't report any messages within the context in which the error occured. That context //is addressed as the base of the stack if (buildContext == errorContext || buildContext.CurrentBuildContextState == ProjectBuildState.BuildContextState.RequestFilled) { continue; } parentEngine.Scheduler.NotifyOfUnblockedRequest(buildContext.BuildRequest); ErrorUtilities.VerifyThrow( buildContext.CurrentBuildContextState == ProjectBuildState.BuildContextState.WaitingForTarget || buildContext == initiatingBuildContext, "This context should be waiting for a target to be evaluated"); if (buildContext.NameOfBlockingTarget == null) { ErrorUtilities.VerifyThrow( String.Compare(EscapingUtilities.UnescapeAll(buildContext.NameOfTargetInProgress), targetClass.Name, StringComparison.OrdinalIgnoreCase) == 0, "The name of the target in progress is inconsistent with the target being built"); // This target was part of a sequential request so we need to notify the parent project // to start building the next target in the sequence if (Engine.debugMode) { Console.WriteLine("Finished " + buildContext.BuildRequest.ProjectFileName + ":" + targetClass.Name + " for node:" + buildContext.BuildRequest.NodeIndex + " HandleId " + buildContext.BuildRequest.HandleId); } } else { // The target on the waiting list must be waiting for this target to complete due to // a dependent or onerror relationship between targets ErrorUtilities.VerifyThrow( String.Compare(buildContext.NameOfBlockingTarget, targetClass.Name, StringComparison.OrdinalIgnoreCase) == 0, "This target should only be updated once the dependent target is completed"); if (Engine.debugMode) { Console.WriteLine("Finished " + targetClass.Name + " notifying " + EscapingUtilities.UnescapeAll(buildContext.NameOfTargetInProgress)); } } // Post a dummy context to the queue to cause the target to run in this context TaskExecutionContext taskExecutionContext = new TaskExecutionContext(parentProject, null, null, buildContext, EngineCallback.invalidEngineHandle, EngineCallback.inProcNode, null); parentEngine.PostTaskOutputUpdates(taskExecutionContext); } }
private void ProcessTaskOutputs(TaskExecutionContext executionContext) { // Get the success or failure if (targetBuildSuccessful) { if (!executionContext.TaskExecutedSuccessfully) { targetBuildSuccessful = false; // Check if the task threw an unhandled exception during its execution if (executionContext.ThrownException != null) { // The stack trace for remote task InvalidProjectFileException can be ignored // since it is not recorded and the exception will be caught inside the project // class if (executionContext.ThrownException is InvalidProjectFileException) { throw executionContext.ThrownException; } else { // The error occured outside of the user code (it may still be caused // by bad user input), the build should be terminated. The exception // will be logged as a fatal build error in engine. The exceptions caused // by user code are converted into LogFatalTaskError messages by the TaskEngine RemoteErrorException.Throw(executionContext.ThrownException, targetBuildEventContext, "RemoteErrorDuringTaskExecution", parentProject.FullFileName, targetClass.Name); } } // We need to disable the execution of the task if it was previously enabled, // and if were only doing execution we can stop processing at the point the // error occurred. If the task fails (which implies that ContinueOnError != 'true'), then do // not execute the remaining tasks because they may depend on the completion // of this task. ErrorUtilities.VerifyThrow(howToBuild == DependencyAnalysisResult.FullBuild || howToBuild == DependencyAnalysisResult.IncrementalBuild, "We can only see a failure for an execution stage"); if (howToBuild != DependencyAnalysisResult.FullBuild) howToBuild = DependencyAnalysisResult.SkipUpToDate; else exitBatchDueToError = true; } } currentTask++; }
private void ContinueRunningTasks ( ProjectBuildState buildContext, TaskExecutionContext taskExecutionContext, bool startingFirstTask ) { bool exitDueToError = true; try { // If this is the first task - initialize for running it if (startingFirstTask) { InitializeForRunningTargetBatches(); } // If run a task then process its outputs if (currentTask != targetElement.ChildNodes.Count && !startingFirstTask) { ProcessTaskOutputs(taskExecutionContext); } // Check if we processed the last node in a batch or terminated the batch due to error if (currentTask == targetElement.ChildNodes.Count || exitBatchDueToError) { FinishRunningSingleTargetBatch(); // On failure transition into unsuccessful state if (!targetBuildSuccessful) { overallSuccess = false; FinishRunningTargetBatches(buildContext); // Transition the state machine into building the error clause state InitializeOnErrorClauseExecution(); inProgressBuildState = InProgressBuildState.BuildingErrorClause; ExecuteErrorTarget(buildContext); exitDueToError = false; return; } //Check if this was the last bucket if (currentBucket == buckets.Count) { FinishRunningTargetBatches(buildContext); inProgressBuildState = InProgressBuildState.NotInProgress; // Notify targets that are waiting for the results NotifyBuildCompletion(Target.BuildState.CompletedSuccessfully, null); exitDueToError = false; return; } // Prepare the next bucket InitializeForRunningSingleTargetBatch(); } // Execute the current task ExecuteCurrentTask(buildContext); exitDueToError = false; } catch (InvalidProjectFileException e) { // Make sure the Invalid Project error gets logged *before* TargetFinished. Otherwise, // the log is confusing. this.parentEngine.LoggingServices.LogInvalidProjectFileError(targetBuildEventContext, e); throw; } finally { if (exitDueToError && loggedTargetStart) { // Log that the target has failed parentEngine.LoggingServices.LogTargetFinished( targetBuildEventContext, targetClass.Name, this.parentProject.FullFileName, targetClass.ProjectFileOfTargetElement, false); } } }
internal void ContinueBuild ( ProjectBuildState buildContext, TaskExecutionContext taskExecutionContext ) { // Verify that the target is in progress ErrorUtilities.VerifyThrow(inProgressBuildState != InProgressBuildState.NotInProgress, "Not in progress"); bool exitedDueToError = true; try { // In the single threaded mode we want to avoid looping all the way back to the // engine because there is no need for to be interruptable to address // other build requests. Instead we loop inside this function untill the target is // fully built. do { // Transition the state machine appropriatly if (inProgressBuildState == InProgressBuildState.RunningTasks) { ContinueRunningTasks(buildContext, taskExecutionContext, false); } else if (inProgressBuildState == InProgressBuildState.BuildingDependencies) { ContinueBuildingDependencies(buildContext); } else if (inProgressBuildState == InProgressBuildState.StartingBuild) { initiatingBuildContext = buildContext; inProgressBuildState = InProgressBuildState.BuildingDependencies; currentDependentTarget = 0; ExecuteDependentTarget(buildContext); } else if (inProgressBuildState == InProgressBuildState.BuildingErrorClause) { ContinueBuildingErrorClause(buildContext); } // In the single threaded mode we need to pull up the outputs of the previous // step if (parentEngine.Router.SingleThreadedMode && inProgressBuildState == InProgressBuildState.RunningTasks) { taskExecutionContext = parentEngine.GetTaskOutputUpdates(); } } while (parentEngine.Router.SingleThreadedMode && inProgressBuildState == InProgressBuildState.RunningTasks); // Indicate that we exited successfully exitedDueToError = false; } finally { if (exitedDueToError) { inProgressBuildState = InProgressBuildState.NotInProgress; NotifyBuildCompletion(Target.BuildState.CompletedUnsuccessfully, buildContext); } } }
internal void PostTaskOutputUpdates(TaskExecutionContext executionContext) { taskOutputUpdates.Enqueue(executionContext); }
/// <summary> /// This method will continue a project build which is in progress /// </summary> private void BuildProjectInternalContinue(BuildRequest buildRequest, ProjectBuildState buildContext, TaskExecutionContext taskExecutionContext, Project project) { if (buildRequest != null && ProfileBuild ) { buildRequest.ProcessingStartTime = DateTime.Now.Ticks; } project.ContinueBuild(buildContext, taskExecutionContext); }
/// <summary> /// Builds the specific targets in an MSBuild project. Since projects can build other projects, this method may get called /// back recursively. It keeps track of the projects being built, so that it knows when we've popped back out to the root /// of the callstack again, so we can reset the state of all the projects. Otherwise, you wouldn't be able to do more /// than one build using the same Engine object, because the 2nd, 3rd, etc. builds would just say "hmm, looks like this /// project has already been built, so I'm not going to build it again". /// </summary> private void BuildProjectInternal ( BuildRequest buildRequest, ProjectBuildState buildContext, TaskExecutionContext taskExecutionContext, bool initialCall ) { Project project = buildRequest.ProjectToBuild; bool exitedDueToError = true; try { SetBuildItemCurrentDirectory(project); if (initialCall) { #if (!STANDALONEBUILD) CodeMarkers.Instance.CodeMarker(CodeMarkerEvent.perfMSBuildEngineBuildProjectBegin); #endif #if MSBUILDENABLEVSPROFILING string beginProjectBuild = String.Format(CultureInfo.CurrentCulture, "Build Project {0} Using Old OM - Start", project.FullFileName); DataCollection.CommentMarkProfile(8802, beginProjectBuild); #endif // Make sure we were passed in a project object. error.VerifyThrowArgument(project != null, "MissingProject", "Project"); // Make sure this project object is associated with this engine object. error.VerifyThrowInvalidOperation(project.ParentEngine == this, "IncorrectObjectAssociation", "Project", "Engine"); } try { if (initialCall) { BuildProjectInternalInitial(buildRequest, project); } else { BuildProjectInternalContinue(buildRequest, buildContext, taskExecutionContext, project); } exitedDueToError = false; } /********************************************************************************************************************** * WARNING: Do NOT add any more catch blocks below! Exceptions should be caught as close to their point of origin as * possible, and converted into one of the known exceptions. The code that causes an exception best understands the * reason for the exception, and only that code can provide the proper error message. We do NOT want to display * messages from unknown exceptions, because those messages are most likely neither localized, nor composed in the * canonical form with the correct prefix. *********************************************************************************************************************/ // Handle errors in the project file. catch (InvalidProjectFileException e) { primaryLoggingServices.LogInvalidProjectFileError(buildRequest.ParentBuildEventContext, e); } // Handle logger failures -- abort immediately catch (LoggerException) { // Polite logger failure throw; } catch (InternalLoggerException) { // Logger threw arbitrary exception throw; } // Handle all other errors. These errors are completely unexpected, so // make sure to give the callstack as well. catch (Exception) { fatalErrorContext = buildRequest.ParentBuildEventContext; fatalErrorProjectName = project.FullFileName; // Rethrow so that the host can catch it and possibly rethrow it again // so that Watson can give the user the option to send us an error report. throw; } /********************************************************************************************************************** * WARNING: Do NOT add any more catch blocks above! *********************************************************************************************************************/ finally { FinishBuildProjectInProgress(buildRequest, buildContext, exitedDueToError); } } finally { // Flush out all the logging messages, which may have been posted outside target execution primaryLoggingServices.ProcessPostedLoggingEvents(); if (buildRequest != null && buildRequest.BuildCompleted || exitedDueToError) { #if (!STANDALONEBUILD) CodeMarkers.Instance.CodeMarker(CodeMarkerEvent.perfMSBuildEngineBuildProjectEnd); #endif #if MSBUILDENABLEVSPROFILING string endProjectBuild = String.Format(CultureInfo.CurrentCulture, "Build Project {0} Using Old OM - End", project.FullFileName); DataCollection.CommentMarkProfile(8803, endProjectBuild); #endif } } }
/// <summary> /// This method is called repeatedly to execute the target in multi-threaded mode. In single /// threaded mode it is called once and it loops internally until the execution is finished. /// </summary> /// <param name="buildContext">Context within which the target is being executed</param> /// <param name="taskExecutionContext">Result of last execution (multi-threaded only)</param> internal void ContinueBuild(ProjectBuildState buildContext, TaskExecutionContext taskExecutionContext) { executionState.ContinueBuild(buildContext, taskExecutionContext); }