File: Engine\ProjectBuildState.cs
Web Access
Project: ..\..\..\src\Deprecated\Engine\Microsoft.Build.Engine.csproj (Microsoft.Build.Engine)
// 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.Diagnostics;
using System.Collections.Generic;
using System.Collections;
 
using Microsoft.Build.Framework;
using Microsoft.Build.BuildEngine.Shared;
 
namespace Microsoft.Build.BuildEngine
{
    /// <summary>
    /// This class is used to maintain a state of execution of a build request. Once a build request is
    /// received it is wrapped in this class
    /// </summary>
    [DebuggerDisplay("ProjectBuildState (NameOfTargetInProgress={NameOfTargetInProgress}, NameOfBlockingTarget={NameOfBlockingTarget}, BuildResult={BuildResult}, BuildComplete={BuildComplete})")]
    internal sealed class ProjectBuildState
    {
        #region Constructors
 
        /// <summary>
        /// Create a build request from the list of targets to build and build request object
        /// </summary>
        internal ProjectBuildState(BuildRequest buildRequest, ArrayList targetNamesToBuild, BuildEventContext buildEventContext)
        {
            this.buildRequest = buildRequest;
            this.indexOfTargetInProgress = 0;
            this.targetNamesToBuild = targetNamesToBuild;
            this.buildContextState = BuildContextState.StartingFirstTarget;
            this.projectBuildEventContext = buildEventContext;
        }
 
        #endregion
 
        #region Properties
        /// <summary>
        /// Current state of the context
        /// </summary>
        internal BuildContextState CurrentBuildContextState
        {
            get
            {
                return this.buildContextState;
            }
            set
            {
                this.buildContextState = value;
            }
        }
 
        /// <summary>
        /// True if the project build context has been completed either successfully or with a failure
        /// </summary>
        internal bool BuildComplete
        {
            get
            {
                return this.buildRequest.BuildCompleted;
            }
            set
            {
                this.buildRequest.BuildCompleted = value;
            }
        }
 
        /// <summary>
        /// True if the project build context has been completed successfully, false otherwise
        /// </summary>
        internal bool BuildResult
        {
            get
            {
                return this.buildRequest.BuildSucceeded;
            }
            set
            {
                this.buildRequest.BuildSucceeded = value;
            }
        }
 
        /// <summary>
        /// Name of the target that blocks the in progress target. Null there is no target
        /// blocking the inprogress target
        /// </summary>
        internal string NameOfBlockingTarget
        {
            get
            {
                return requiredTargets?.Count > 0 ?
                        this.requiredTargets.Peek() : null;
            }
        }
 
        /// <summary>
        /// Name of the target currently in progress
        /// </summary>
        internal string NameOfTargetInProgress
        {
            get
            {
                return (string)this.targetNamesToBuild[indexOfTargetInProgress];
            }
        }
 
        /// <summary>
        /// List of targets that need to be completed in order to complete the context
        /// </summary>
        internal ArrayList TargetNamesToBuild
        {
            get
            {
                return this.targetNamesToBuild;
            }
        }
 
        /// <summary>
        /// Build request that caused the context to come into existance (either from the host or generated)
        /// </summary>
        internal BuildRequest BuildRequest
        {
            get
            {
                return this.buildRequest;
            }
        }
 
        #endregion
 
        #region Methods
        /// <summary>
        /// Move to the next target in the context. Return null if there is no next target
        /// </summary>
        /// <returns></returns>
        internal string GetNextTarget()
        {
            if ((indexOfTargetInProgress + 1) < targetNamesToBuild.Count)
            {
                indexOfTargetInProgress++;
                return (string)targetNamesToBuild[indexOfTargetInProgress];
            }
            else
            {
                // All targets in this context have been build
                return null;
            }
        }
 
        /// <summary>
        /// Remove the top blocking target
        /// </summary>
        internal void RemoveBlockingTarget()
        {
            ErrorUtilities.VerifyThrow(requiredTargets.Count > 0, "No target to remove");
            requiredTargets.Pop();
        }
 
        /// <summary>
        /// Add another blocking target
        /// </summary>
        internal void AddBlockingTarget(string targetName)
        {
            if (requiredTargets == null)
            {
                requiredTargets = new Stack<string>();
            }
            buildContextState = BuildContextState.StartingBlockingTarget;
            requiredTargets.Push(targetName);
        }
 
        /// <summary>
        /// Marks the build context and build result as complete with the given result
        /// </summary>
        internal void RecordBuildCompletion(bool result)
        {
            buildContextState = ProjectBuildState.BuildContextState.BuildComplete;
            buildRequest.BuildCompleted = true;
            buildRequest.BuildSucceeded = result;
        }
 
        /// <summary>
        /// Marks the build context and build result appropriate for an exception thrown within
        /// a build context
        /// </summary>
        internal void RecordBuildException()
        {
            buildContextState = ProjectBuildState.BuildContextState.ExceptionThrown;
            buildRequest.BuildCompleted = true;
            buildRequest.BuildSucceeded = false;
        }
 
        #region Methods used for cycle detection only
        /// <summary>
        /// This method returns true if the top blocking target appears in the stack of
        /// blocking targets more than once, thus forming a cycle.
        /// </summary>
        internal bool ContainsCycle(string name)
        {
            bool containsCycle = false;
            if (requiredTargets?.Count > 1)
            {
                string topTarget = requiredTargets.Pop();
                ErrorUtilities.VerifyThrow(topTarget == name, "Requesting target should be on the top of stack");
                containsCycle = requiredTargets.Contains(name);
                requiredTargets.Push(topTarget);
            }
            if (!containsCycle && requiredTargets?.Count > 0)
            {
                containsCycle =
                    (String.Equals(name, (string)targetNamesToBuild[indexOfTargetInProgress], StringComparison.OrdinalIgnoreCase));
            }
            return containsCycle;
        }
 
        /// <summary>
        /// This method return true if a given target name appears anywhere in the list of
        /// blocking targets
        /// </summary>
        internal bool ContainsBlockingTarget(string name)
        {
            bool containsName = false;
            if (requiredTargets?.Count > 0)
            {
                containsName = requiredTargets.Contains(name);
            }
            return containsName;
        }
 
        /// <summary>
        /// This method is used by the target cycle detector to find the parent target for
        /// the given target. The parent only exists if there is at least 1
        /// blocking target. If there is less than 1 blocking target the parent is determined
        /// by the orgin of the build request that caused this build context.
        /// </summary>
        internal string GetParentTarget(string name)
        {
            string parentName = null;
            if (requiredTargets?.Count > 0)
            {
                parentName = (string)targetNamesToBuild[indexOfTargetInProgress];
 
                if (requiredTargets.Count > 1)
                {
                    string[] requiredTargetsArray = requiredTargets.ToArray();
 
                    for (int i = requiredTargetsArray.Length - 1; i >= 0; i--)
                    {
                        if (string.CompareOrdinal(requiredTargetsArray[i], name) != 0)
                        {
                            parentName = requiredTargetsArray[i];
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }
            return parentName;
        }
 
        /// <summary>
        /// Contains the event context for the build context, this is created when the ProjectBuildState is created
        /// </summary>
        internal BuildEventContext ProjectBuildEventContext
        {
            get
            {
                return this.projectBuildEventContext;
            }
        }
        #endregion
 
        #endregion
 
        #region Enums
        /// <summary>
        /// States of execution of a build request
        /// </summary>
        internal enum BuildContextState
        {
            /// <summary>
            /// Starting the first target within the request. Default initial state.
            /// </summary>
            StartingFirstTarget,
            /// <summary>
            /// Starting a target due to a depends on or on error relationship
            /// </summary>
            StartingBlockingTarget,
            /// <summary>
            /// The target which is needed is already in progress due to another request, so wait for a result
            /// </summary>
            WaitingForTarget,
            /// <summary>
            /// Currently in progress of building a needed target
            /// </summary>
            BuildingCurrentTarget,
            /// <summary>
            /// Cycle is detected and is caused by this request
            /// </summary>
            CycleDetected,
            /// <summary>
            /// There is an exception thrown during the execution of this request
            /// </summary>
            ExceptionThrown,
            /// <summary>
            /// All needed target have been completed or an error terminating the request has occurred
            /// </summary>
            BuildComplete,
            /// <summary>
            /// The result of the request has been sent back to the requesting party
            /// </summary>
            RequestFilled
        }
        #endregion
 
        #region Data
        // Stack of targets which need to be completed before the in progress target can continue
        private Stack<string> requiredTargets;
 
        // BuildEventContext for the build context
        private BuildEventContext projectBuildEventContext;
 
        // Index of the currently in progress target
        private int indexOfTargetInProgress;
 
        // List of targets that need to be completed in order to complete the context
        // UNDONE should do the right thing and fully unescape before generating this list
        private ArrayList targetNamesToBuild;
 
        // Build request that caused the context to come into existance (either from the host or generated)
        private BuildRequest buildRequest;
 
        // Current state of the context
        private BuildContextState buildContextState;
        #endregion
    }
}