|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// THE ASSEMBLY BUILT FROM THIS SOURCE FILE HAS BEEN DEPRECATED FOR YEARS. IT IS BUILT ONLY TO PROVIDE
// BACKWARD COMPATIBILITY FOR API USERS WHO HAVE NOT YET MOVED TO UPDATED APIS. PLEASE DO NOT SEND PULL
// REQUESTS THAT CHANGE THIS FILE WITHOUT FIRST CHECKING WITH THE MAINTAINERS THAT THE FIX IS REQUIRED.
using System;
using System.Xml;
using System.Diagnostics; // for the debugger display attribute
using System.Collections;
using System.Collections.Generic;
using Microsoft.Build.Framework;
using Microsoft.Build.BuildEngine.Shared;
namespace Microsoft.Build.BuildEngine
{
/// <summary>
/// This class is responsible for executing the target. Target only executs once within a project,
/// so this class comes into existance for the execution and is thrown away once the execution is
/// complete. It wraps all of the data and methods needed to execute a target. The execution
/// is done via state machine with three primary states - BuildingDependencies, RunningTasks,
/// BuildingErrorClause. This states map to the primary actions that are performed during target
/// execution. The execution is blocking in single threaded mode and is iterative in multi-threaded
/// mode.
/// </summary>
[DebuggerDisplay("Target (Name = { Name }, State = { inProgressBuildState })")]
internal class TargetExecutionWrapper
{
#region Constructors
internal TargetExecutionWrapper
(
Target targetClass,
ArrayList taskElementList,
List<string> targetParameters,
XmlElement targetElement,
Expander expander,
BuildEventContext targetBuildEventContext
)
{
// Initialize the data about the target XML that has been calculated in the target class
this.targetClass = targetClass;
this.parentEngine = targetClass.ParentEngine;
this.parentProject = targetClass.ParentProject;
this.targetElement = targetElement;
this.taskElementList = taskElementList;
this.targetParameters = targetParameters;
this.targetBuildEventContext = targetBuildEventContext;
// Expand the list of depends on targets
dependsOnTargetNames = expander.ExpandAllIntoStringList(targetClass.DependsOnTargets, targetClass.DependsOnTargetsAttribute);
// Starting to build the target
inProgressBuildState = InProgressBuildState.StartingBuild;
// No messages have been logged
loggedTargetStart = false;
}
#endregion
#region Data
// Local cache of data from the target being executed
private Target targetClass;
private Engine parentEngine;
private Project parentProject;
private XmlElement targetElement;
private ArrayList taskElementList;
private List<string> targetParameters;
private ProjectBuildState initiatingBuildContext;
private BuildEventContext targetBuildEventContext;
// Current state of the execution
private InProgressBuildState inProgressBuildState;
private bool overallSuccess;
// the outputs of the target as BuildItems (if it builds successfully)
private List<BuildItem> targetOutputItems;
private List<ProjectBuildState> waitingTargets;
// Names of the dependent targets
private List<string> dependsOnTargetNames;
private int currentDependentTarget;
// Names of the error targets (lazily initialized on error)
private List<string> onErrorTargets;
private int currentErrorTarget;
// Array of buckets and the index of current bucket
private ArrayList buckets;
private int currentBucket;
private bool haveRunANonIntrinsicTask = false;
// Lookup containing project content used to
// initialize the target batches
private Lookup projectContent;
private LookupEntry placeholderForClonedProjectContent;
// State for execution within a particular bucket
private DependencyAnalysisResult howToBuild;
private Lookup lookupForInference;
private Lookup lookupForExecution;
private string projectFileOfTaskNode;
private int currentTask;
private int skippedNodeCount;
private bool targetBuildSuccessful;
private bool exitBatchDueToError;
private bool loggedTargetStart;
#endregion
#region Properties
internal ProjectBuildState InitiatingBuildContext
{
get
{
return this.initiatingBuildContext;
}
}
internal bool BuildingRequiredTargets
{
get
{
return inProgressBuildState == InProgressBuildState.BuildingDependencies ||
inProgressBuildState == InProgressBuildState.BuildingErrorClause;
}
}
#endregion
#region Methods
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);
}
}
}
/// <summary>
/// Mark the target data structures and notify waiting targets since the target has completed
/// </summary>
internal void NotifyBuildCompletion
(
Target.BuildState stateOfBuild,
ProjectBuildState errorContext
)
{
targetClass.UpdateTargetStateOnBuildCompletion(stateOfBuild, targetOutputItems);
if (initiatingBuildContext.NameOfBlockingTarget == null)
{
initiatingBuildContext.BuildRequest.ResultByTarget[targetClass.Name] = stateOfBuild;
}
if (!parentEngine.Router.SingleThreadedMode)
{
// Notify targets that have been waiting on the execution
NotifyWaitingTargets(errorContext);
}
}
#region Methods for building dependencies ( InProgressBuildState.BuildingDependencies )
private void ContinueBuildingDependencies(ProjectBuildState buildContext)
{
// Verify that the target is in the right state
ErrorUtilities.VerifyThrow(inProgressBuildState == InProgressBuildState.BuildingDependencies, "Wrong state");
// Check if all dependent targets have been evaluated
ErrorUtilities.VerifyThrow(currentDependentTarget < dependsOnTargetNames.Count, "No dependent targets left");
// Verify that the target we were waiting on has completed building
string nameDependentTarget = dependsOnTargetNames[currentDependentTarget];
ErrorUtilities.VerifyThrow(
(parentProject.Targets[nameDependentTarget].TargetBuildState != Target.BuildState.InProgress &&
parentProject.Targets[nameDependentTarget].TargetBuildState != Target.BuildState.NotStarted) ||
buildContext.CurrentBuildContextState == ProjectBuildState.BuildContextState.ExceptionThrown,
"This target should only be updated once the dependent target is completed");
if (buildContext.CurrentBuildContextState == ProjectBuildState.BuildContextState.ExceptionThrown)
{
inProgressBuildState = InProgressBuildState.NotInProgress;
// Call the parent project to update targets waiting on us
NotifyBuildCompletion(Target.BuildState.CompletedUnsuccessfully, buildContext);
return;
}
// If the dependent target failed to build we need to execute the onerrorclause (if there is one)
// or mark this target as failed (if there is not an error clause)
else if (parentProject.Targets[nameDependentTarget].TargetBuildState == Target.BuildState.CompletedUnsuccessfully)
{
// Transition the state machine into building the error clause state
InitializeOnErrorClauseExecution();
inProgressBuildState = InProgressBuildState.BuildingErrorClause;
ExecuteErrorTarget(buildContext);
return;
}
// Now that the previous dependent target has been build we need to move to the next dependent target if
// there is one
currentDependentTarget++;
// Execute the current target or transition to a different state if necessary
ExecuteDependentTarget(buildContext);
}
private void ExecuteDependentTarget
(
ProjectBuildState buildContext
)
{
if (currentDependentTarget < dependsOnTargetNames.Count)
{
// Get the Target object for the dependent target.
string nameDependentTarget = dependsOnTargetNames[currentDependentTarget];
Target targetToBuild = parentProject.Targets[nameDependentTarget];
// If we couldn't find the dependent Target object, we have a problem.
ProjectErrorUtilities.VerifyThrowInvalidProject(targetToBuild != null, targetClass.DependsOnTargetsAttribute,
"TargetDoesNotExist", nameDependentTarget);
// Update the name of the blocking target
buildContext.AddBlockingTarget(nameDependentTarget);
}
else
{
// We completed building the dependencies so we need to start running the tasks
dependsOnTargetNames = null;
inProgressBuildState = InProgressBuildState.RunningTasks;
ContinueRunningTasks(buildContext, null, true);
}
}
#endregion
#region Methods for build error targets ( InProgressBuildState.BuildingErrorClause )
private void ContinueBuildingErrorClause(ProjectBuildState buildContext)
{
// Verify that the target is in the right state
ErrorUtilities.VerifyThrow(inProgressBuildState == InProgressBuildState.BuildingErrorClause, "Wrong state");
// Check if all dependent targets have been evaluated
ErrorUtilities.VerifyThrow(currentErrorTarget < onErrorTargets.Count, "No error targets left");
// Verify that the target we were waiting on has completed building
string nameErrorTarget = onErrorTargets[currentErrorTarget];
ErrorUtilities.VerifyThrow(
(parentProject.Targets[nameErrorTarget].TargetBuildState != Target.BuildState.InProgress &&
parentProject.Targets[nameErrorTarget].TargetBuildState != Target.BuildState.NotStarted) ||
buildContext.CurrentBuildContextState == ProjectBuildState.BuildContextState.ExceptionThrown,
"This target should only be updated once the error target is completed");
if (buildContext.CurrentBuildContextState == ProjectBuildState.BuildContextState.ExceptionThrown)
{
inProgressBuildState = InProgressBuildState.NotInProgress;
// Call the parent project to update targets waiting on us
NotifyBuildCompletion(Target.BuildState.CompletedUnsuccessfully, buildContext);
return;
}
// We don't care if the target has completed successfully, we simply move on to the next one
currentErrorTarget++;
ExecuteErrorTarget(buildContext);
}
private void ExecuteErrorTarget
(
ProjectBuildState buildContext
)
{
if (onErrorTargets != null && currentErrorTarget < onErrorTargets.Count)
{
// Get the Target object for the dependent target.
string nameErrorTarget = onErrorTargets[currentErrorTarget];
Target targetToBuild = parentProject.Targets[nameErrorTarget];
// If we couldn't find the on error Target object, we have a problem.
ProjectErrorUtilities.VerifyThrowInvalidProject(targetToBuild != null, targetElement,
"TargetDoesNotExist", nameErrorTarget);
// Update the name of the blocking target
buildContext.AddBlockingTarget(nameErrorTarget);
}
else
{
// We completed building the error targets so this target is now failed and we have no more work to do
onErrorTargets = null;
inProgressBuildState = InProgressBuildState.NotInProgress;
// Call the parent project to update targets waiting on us
NotifyBuildCompletion(Target.BuildState.CompletedUnsuccessfully, null);
}
}
/// <summary>
/// Creates a list of targets to execute for the OnErrorClause
/// </summary>
private void InitializeOnErrorClauseExecution()
{
// Give default values;
currentErrorTarget = 0;
onErrorTargets = null;
// Loop through each of the child nodes of the <target> element.
List<XmlElement> childElements = ProjectXmlUtilities.GetValidChildElements(targetElement);
foreach (XmlElement childElement in childElements)
{
switch (childElement.Name)
{
case XMakeElements.onError:
ProjectXmlUtilities.VerifyThrowProjectNoChildElements(childElement);
XmlAttribute condition = null;
XmlAttribute executeTargets = null;
foreach (XmlAttribute onErrorAttribute in childElement.Attributes)
{
switch (onErrorAttribute.Name)
{
case XMakeAttributes.condition:
condition = childElement.Attributes[XMakeAttributes.condition];
break;
case XMakeAttributes.executeTargets:
executeTargets = childElement.Attributes[XMakeAttributes.executeTargets];
break;
default:
ProjectXmlUtilities.ThrowProjectInvalidAttribute(onErrorAttribute);
break;
}
}
ProjectErrorUtilities.VerifyThrowInvalidProject(executeTargets != null, childElement, "MissingRequiredAttribute", XMakeAttributes.executeTargets, XMakeElements.onError);
Expander expander = new Expander(this.parentProject.evaluatedProperties, this.parentProject.evaluatedItemsByName);
bool runErrorTargets = true;
if (condition != null)
{
if
(
!Utilities.EvaluateCondition
(
condition.InnerText, condition, expander,
null, ParserOptions.AllowProperties | ParserOptions.AllowItemLists,
parentEngine.LoggingServices, targetBuildEventContext
)
)
{
runErrorTargets = false;
}
}
if (runErrorTargets)
{
if (onErrorTargets == null)
{
onErrorTargets = expander.ExpandAllIntoStringList(executeTargets.InnerText, executeTargets);
}
else
{
onErrorTargets.AddRange(expander.ExpandAllIntoStringList(executeTargets.InnerText, executeTargets));
}
}
break;
default:
// Ignore
break;
}
}
}
#endregion
#region Methods for running tasks ( InProgressBuildState.RunningTasks )
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);
}
}
}
private void InitializeForRunningTargetBatches()
{
// Make sure the <target> node has been given to us.
ErrorUtilities.VerifyThrow(targetElement != null,
"Need an XML node representing the <target> element.");
// Make sure this really is the <target> node.
ProjectXmlUtilities.VerifyThrowElementName(targetElement, XMakeElements.target);
overallSuccess = true;
projectContent = new Lookup(parentProject.evaluatedItemsByName, parentProject.evaluatedItems, parentProject.evaluatedProperties, parentProject.ItemDefinitionLibrary);
// If we need to use the task thread - ie, we encounter a non-intrinsic task - we will need to make sure
// the task thread only sees clones of the project items and properties. We insert a scope to allow us to
// do that later. See comment in InitializeForRunningFirstNonIntrinsicTask()
placeholderForClonedProjectContent = projectContent.EnterScope();
buckets = BatchingEngine.PrepareBatchingBuckets(targetElement, targetParameters, projectContent);
currentBucket = 0;
// Initialize the first bucket
InitializeForRunningSingleTargetBatch();
}
private void InitializeForRunningSingleTargetBatch()
{
// Verify that the target is in the right state
ErrorUtilities.VerifyThrow(inProgressBuildState == InProgressBuildState.RunningTasks, "Wrong state");
// Check if the current task number is valid
ErrorUtilities.VerifyThrow(currentBucket < buckets.Count, "No buckets left");
howToBuild = DependencyAnalysisResult.FullBuild;
ItemBucket bucket = (ItemBucket)buckets[currentBucket];
// For the first batch of a target use the targets original targetID. for each batch after the first one use a uniqueId to identity the target in the batch
if (currentBucket != 0)
{
targetBuildEventContext = new BuildEventContext(targetBuildEventContext.NodeId, parentEngine.GetNextTargetId(), targetBuildEventContext.ProjectContextId, targetBuildEventContext.TaskId);
}
// Flag the start of the target.
parentEngine.LoggingServices.LogTargetStarted(
targetBuildEventContext,
targetClass.Name,
this.parentProject.FullFileName,
targetClass.ProjectFileOfTargetElement);
loggedTargetStart = true;
// Figure out how we should build the target
TargetDependencyAnalyzer dependencyAnalyzer = new TargetDependencyAnalyzer(parentProject.ProjectDirectory, targetClass, parentEngine.LoggingServices, targetBuildEventContext);
Hashtable changedTargetInputs;
Hashtable upToDateTargetInputs;
howToBuild = dependencyAnalyzer.PerformDependencyAnalysis(bucket, out changedTargetInputs, out upToDateTargetInputs);
targetBuildSuccessful = true;
exitBatchDueToError = false;
// If we need to build the target - initialize the data structure for
// running the tasks
if ((howToBuild != DependencyAnalysisResult.SkipNoInputs) &&
(howToBuild != DependencyAnalysisResult.SkipNoOutputs))
{
// Within each target batch items are divided into lookup and execution; they must be
// kept separate: enforce this by cloning and entering scope
lookupForInference = bucket.Lookup;
lookupForExecution = bucket.Lookup.Clone();
lookupForInference.EnterScope();
lookupForExecution.EnterScope();
// 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 (howToBuild == DependencyAnalysisResult.IncrementalBuild)
{
// subset the relevant items to those that are up-to-date
foreach (DictionaryEntry upToDateTargetInputsEntry in upToDateTargetInputs)
{
lookupForInference.PopulateWithItems((string)upToDateTargetInputsEntry.Key, (BuildItemGroup)upToDateTargetInputsEntry.Value);
}
// subset the relevant items to those that have changed
foreach (DictionaryEntry changedTargetInputsEntry in changedTargetInputs)
{
lookupForExecution.PopulateWithItems((string)changedTargetInputsEntry.Key, (BuildItemGroup)changedTargetInputsEntry.Value);
}
}
projectFileOfTaskNode = XmlUtilities.GetXmlNodeFile(targetElement, parentProject.FullFileName);
// count the tasks in the target
currentTask = 0;
skippedNodeCount = 0;
}
else
{
currentTask = targetElement.ChildNodes.Count;
}
}
/// <summary>
/// Called before the first non-intrinsic task is run by this object.
/// </summary>
private void InitializeForRunningFirstNonIntrinsicTask()
{
// We need the task thread to see cloned project content for two reasons:
// (1) clone items because BuildItemGroups storage is a List, which
// is not safe to read from (task thread) and write to (engine thread) concurrently.
// Project properties are in a virtual property group which stores its properties in a hashtable, however
// (2) we must clone both items and properties so that project items and properties modified by a
// target called by this target are not visible to this target (Whidbey behavior)
//
// So, we populate the empty scope we inserted earlier with clones of the items and properties, and we
// mark it so that lookups truncate their walk at this scope, and don't reach the real items and properties below.
// Later, back on the engine thread, we'll leave this scope and the task changes will go into the project.
Hashtable items = new Hashtable(parentProject.evaluatedItemsByName.Count, StringComparer.OrdinalIgnoreCase);
foreach (DictionaryEntry entry in parentProject.evaluatedItemsByName)
{
BuildItemGroup group = (BuildItemGroup)entry.Value;
BuildItemGroup clonedGroup = group.ShallowClone();
items.Add(entry.Key, clonedGroup);
}
BuildPropertyGroup properties = parentProject.evaluatedProperties.ShallowClone();
placeholderForClonedProjectContent.Items = items;
placeholderForClonedProjectContent.Properties = properties;
placeholderForClonedProjectContent.TruncateLookupsAtThisScope = true;
}
/// <summary>
/// Executes all tasks in the target linearly from beginning to end, for one batch of the target.
/// </summary>
private void ExecuteCurrentTask(ProjectBuildState buildContext)
{
// Check if this is an empty target
if (currentTask == targetElement.ChildNodes.Count)
{
// This is an empty target so we should transition into completed state
ContinueRunningTasks(buildContext, null, false);
return;
}
// Get the current child nodes of the <Target> element.
XmlNode targetChildNode = targetElement.ChildNodes[currentTask];
// Handle XML comments under the <target> node (just ignore them) and
// also skip OnError tags because they are processed separately and later.
// Also evaluate any intrinsic tasks immediately and continue.
while ((targetChildNode.NodeType == XmlNodeType.Comment) ||
(targetChildNode.NodeType == XmlNodeType.Whitespace) ||
(targetChildNode.Name == XMakeElements.onError) ||
(IntrinsicTask.IsIntrinsicTaskName(targetChildNode.Name)))
{
if (IntrinsicTask.IsIntrinsicTaskName(targetChildNode.Name))
{
ExecuteIntrinsicTask((XmlElement)targetChildNode);
}
else
{
skippedNodeCount++;
}
currentTask++;
// Check if this was the last task in the target
if (currentTask == targetElement.ChildNodes.Count)
{
// Transition into appropriate state
ContinueRunningTasks(buildContext, null, false);
return;
}
targetChildNode = targetElement.ChildNodes[currentTask];
}
// Any child node other than a task element or <OnError> is not supported.
ProjectXmlUtilities.VerifyThrowProjectXmlElementChild(targetChildNode);
// Make <ItemDefinitionGroup> illegal inside targets, so we can possibly allow it in future.
ProjectErrorUtilities.VerifyThrowInvalidProject(!String.Equals(targetChildNode.Name, XMakeElements.itemDefinitionGroup, StringComparison.Ordinal),
targetElement, "ItemDefinitionGroupNotLegalInsideTarget", targetChildNode.Name, XMakeElements.target);
ErrorUtilities.VerifyThrow(taskElementList.Count > (currentTask - skippedNodeCount),
"The TaskElementCollection in this target doesn't have the same number of BuildTask objects as the number of actual task elements.");
// Send the task for execution
SubmitNonIntrinsicTask(
(XmlElement)targetChildNode,
((BuildTask)taskElementList[currentTask - skippedNodeCount]).HostObject,
buildContext);
return;
}
private TaskExecutionMode DetermineExecutionMode()
{
TaskExecutionMode executionMode;
if ((howToBuild == DependencyAnalysisResult.SkipUpToDate) ||
(howToBuild == DependencyAnalysisResult.IncrementalBuild))
{
executionMode = TaskExecutionMode.InferOutputsOnly;
}
else
{
executionMode = TaskExecutionMode.ExecuteTaskAndGatherOutputs;
}
// execute the task using the items that need to be (re)built
if ((howToBuild == DependencyAnalysisResult.FullBuild) ||
(howToBuild == DependencyAnalysisResult.IncrementalBuild))
{
executionMode |= TaskExecutionMode.ExecuteTaskAndGatherOutputs;
}
return executionMode;
}
/// <summary>
/// Create a new build event context for tasks
/// </summary>
private BuildEventContext PrepareBuildEventContext(bool setInvalidTaskId)
{
BuildEventContext buildEventContext = new BuildEventContext
(
targetBuildEventContext.NodeId,
targetBuildEventContext.TargetId,
targetBuildEventContext.ProjectContextId,
setInvalidTaskId ? BuildEventContext.InvalidTaskId : parentEngine.GetNextTaskId()
);
return buildEventContext;
}
private void ExecuteIntrinsicTask(XmlElement taskNode)
{
// Intrinsic tasks should have their messages logged in the context of the target as they will not have task started or finished events so use an invalid taskID
BuildEventContext buildEventContext = PrepareBuildEventContext(true);
TaskExecutionMode executionMode = DetermineExecutionMode();
IntrinsicTask task = new IntrinsicTask(taskNode,
parentEngine.LoggingServices,
buildEventContext,
parentProject.ProjectDirectory,
parentProject.ItemDefinitionLibrary);
if ((executionMode & TaskExecutionMode.InferOutputsOnly) != TaskExecutionMode.Invalid)
{
task.ExecuteTask(lookupForInference);
}
if ((executionMode & TaskExecutionMode.ExecuteTaskAndGatherOutputs) != TaskExecutionMode.Invalid)
{
task.ExecuteTask(lookupForExecution);
}
}
/// <summary>
/// Create a TaskExecutionState structure which contains all the information necessary
/// to execute the task and send this information over to the TEM for task execution
/// </summary>
internal void SubmitNonIntrinsicTask
(
XmlElement taskNode,
ITaskHost hostObject,
ProjectBuildState buildContext
)
{
if (!haveRunANonIntrinsicTask)
{
InitializeForRunningFirstNonIntrinsicTask();
haveRunANonIntrinsicTask = true;
}
TaskExecutionMode executionMode = DetermineExecutionMode();
// A TaskExecutionMode of ExecuteTaskAndGatherOutputs should have its messages logged in the context of the task and therefore should have a valid taskID
// A TaskExecutionMode of InferOutputs or Invalid should have its messages logged in the context of the target and therefore should have an invalid taskID
BuildEventContext buildEventContext = PrepareBuildEventContext(executionMode == TaskExecutionMode.ExecuteTaskAndGatherOutputs ? false : true);
// Create the task execution context
int handleId = parentEngine.EngineCallback.CreateTaskContext(parentProject, targetClass, buildContext,
taskNode, EngineCallback.inProcNode, buildEventContext);
// Create the task execution state
TaskExecutionState taskState =
new TaskExecutionState
(
executionMode,
lookupForInference,
lookupForExecution,
taskNode,
hostObject,
projectFileOfTaskNode,
parentProject.FullFileName,
parentProject.ProjectDirectory,
handleId,
buildEventContext
);
// Send the request for task execution to the node
parentEngine.NodeManager.ExecuteTask(taskState);
}
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 occurred 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 FinishRunningSingleTargetBatch
(
)
{
if ((howToBuild != DependencyAnalysisResult.SkipNoInputs) &&
(howToBuild != DependencyAnalysisResult.SkipNoOutputs))
{
// publish all output items and properties to the target scope;
// inference and execution are now combined
// roll up the outputs in the right order -- inferred before generated
// 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
lookupForInference.LeaveScope();
lookupForExecution.LeaveScope();
}
// Flag the completion of the target.
parentEngine.LoggingServices.LogTargetFinished(
targetBuildEventContext,
targetClass.Name,
this.parentProject.FullFileName,
targetClass.ProjectFileOfTargetElement,
overallSuccess && targetBuildSuccessful);
loggedTargetStart = false;
// Get the next bucket
currentBucket++;
}
private void FinishRunningTargetBatches(ProjectBuildState buildContext)
{
// first, publish all task outputs to the project level
foreach (ItemBucket bucket in buckets)
{
bucket.Lookup.LeaveScope();
}
// and also leave the extra scope we created with the cloned project items
projectContent.LeaveScope();
// if all batches of the target build successfully
if (overallSuccess)
{
// then, gather the target outputs
// NOTE: it is possible that the target outputs computed at this point will be different from the target outputs
// used for dependency analysis, but we assume that's what the user intended
GatherTargetOutputs();
// Only contexts which are generated from an MSBuild task could need
// the outputs of the target, such contexts have a non-null evaluation
// request
if (buildContext.BuildRequest.OutputsByTarget != null &&
buildContext.NameOfBlockingTarget == null)
{
ErrorUtilities.VerifyThrow(
String.Equals(EscapingUtilities.UnescapeAll(buildContext.NameOfTargetInProgress), targetClass.Name, StringComparison.OrdinalIgnoreCase),
"The name of the target in progress is inconsistent with the target being built");
ErrorUtilities.VerifyThrow(targetOutputItems != null,
"If the target built successfully, we must have its outputs.");
buildContext.BuildRequest.OutputsByTarget[targetClass.Name] = targetOutputItems.ToArray();
}
}
}
/// <summary>
/// Gathers the target's outputs, per its output specification (if any).
/// </summary>
/// <remarks>
/// This method computes the target's outputs using the items currently available in the project; depending on when this
/// method is called, it may compute a different set of outputs -- as a result, we only want to gather the target's
/// outputs once, and cache them until the target's build state is reset.
/// </remarks>
private void GatherTargetOutputs()
{
// allocate storage for target outputs -- if the target has no outputs this list will remain empty
targetOutputItems = new List<BuildItem>();
XmlAttribute targetOutputsAttribute = targetElement.Attributes[XMakeAttributes.outputs];
// Hack to help the 3.5 engine at least pretend to still be able to build on top of
// the 4.0 targets. In cases where there is no Outputs attribute, just a Returns attribute,
// we can approximate the correct behaviour by making the Returns attribute our "outputs" attribute.
if (targetOutputsAttribute == null)
{
targetOutputsAttribute = targetElement.Attributes[XMakeAttributes.returns];
}
if (targetOutputsAttribute != null)
{
// NOTE: we need to gather the outputs in batches, because the output specification may reference item metadata
foreach (ItemBucket bucket in BatchingEngine.PrepareBatchingBuckets(targetElement, targetParameters, new Lookup(parentProject.evaluatedItemsByName, parentProject.evaluatedProperties, parentProject.ItemDefinitionLibrary)))
{
targetOutputItems.AddRange(bucket.Expander.ExpandAllIntoBuildItems(targetOutputsAttribute.Value, targetOutputsAttribute));
}
}
}
#endregion
#region Methods for managing the wait states
/// <summary>
/// Add a build context that should get a result of the target once it is finished
/// </summary>
internal void AddWaitingBuildContext(ProjectBuildState buildContext)
{
if (waitingTargets == null)
{
waitingTargets = new List<ProjectBuildState>();
}
parentEngine.Scheduler.NotifyOfBlockedRequest(buildContext.BuildRequest);
waitingTargets.Add(buildContext);
}
/// <summary>
/// Get the list of build contexts currently waiting on the target
/// </summary>
internal List<ProjectBuildState> GetWaitingBuildContexts()
{
return waitingTargets;
}
/// <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 occurred. 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.Equals(EscapingUtilities.UnescapeAll(buildContext.NameOfTargetInProgress), targetClass.Name, StringComparison.OrdinalIgnoreCase),
"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.Equals(buildContext.NameOfBlockingTarget, targetClass.Name, StringComparison.OrdinalIgnoreCase),
"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);
}
}
#endregion
#endregion
#region Enums
internal enum InProgressBuildState
{
// This target is not in the process of building
NotInProgress,
// The target is being started but no work has been done
StartingBuild,
// This target is in process of building dependencies
BuildingDependencies,
// This target is in process of building the error clause
BuildingErrorClause,
// This target is current running the tasks for each bucket
RunningTasks
}
#endregion
}
}
|