File: BackEnd\Shared\BuildResult.cs
Web Access
Project: ..\..\..\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
using Microsoft.Build.Framework;
 
namespace Microsoft.Build.Execution
{
    /// <summary>
    /// Overall results for targets and requests
    /// </summary>
    public enum BuildResultCode
    {
        /// <summary>
        /// The target or request was a complete success.
        /// </summary>
        Success,
 
        /// <summary>
        /// The target or request failed in some way.
        /// </summary>
        Failure
    }
 
    /// <summary>
    /// Contains the current results for all of the targets which have produced results for a particular configuration.
    /// </summary>
    /// When modifying serialization/deserialization, bump the version and support previous versions in order to keep <see cref="ResultsCache"/> backwards compatible.
    public class BuildResult : BuildResultBase, INodePacket, IBuildResults
    {
        /// <summary>
        /// The submission with which this result is associated.
        /// </summary>
        private int _submissionId;
 
        /// <summary>
        /// The configuration ID with which this result is associated.
        /// </summary>
        private int _configurationId;
 
        /// <summary>
        /// The global build request ID for which these results are intended.
        /// </summary>
        private int _globalRequestId;
 
        /// <summary>
        /// The global build request ID which issued the request leading to this result.
        /// </summary>
        private int _parentGlobalRequestId;
 
        /// <summary>
        /// The build request ID on the originating node.
        /// </summary>
        private int _nodeRequestId;
 
        /// <summary>
        /// The first build request to generate results for a configuration will set this so that future
        /// requests may be properly satisfied from the cache.
        /// </summary>
        private List<string>? _initialTargets;
 
        /// <summary>
        /// The first build request to generate results for a configuration will set this so that future
        /// requests may be properly satisfied from the cache.
        /// </summary>
        private List<string>? _defaultTargets;
 
        /// <summary>
        /// The set of results for each target.
        /// </summary>
        private ConcurrentDictionary<string, TargetResult> _resultsByTarget;
 
        /// <summary>
        /// Version of the build result.
        /// </summary>
        /// <remarks>
        /// Allows to serialize and deserialize different versions of the build result.
        /// </remarks>
        private int _version = Traits.Instance.EscapeHatches.DoNotVersionBuildResult ? 0 : 1;
 
        /// <summary>
        /// The request caused a circular dependency in scheduling.
        /// </summary>
        private bool _circularDependency;
 
        /// <summary>
        /// The exception generated while this request was running, if any.
        /// Note that this can be set if the request itself fails, or if it receives
        /// an exception from a target or task.
        /// </summary>
        private Exception? _requestException;
 
        /// <summary>
        /// The overall result calculated in the constructor.
        /// </summary>
        private bool _baseOverallResult = true;
 
        /// <summary>
        /// Snapshot of the environment from the configuration this results comes from.
        /// This should only be populated when the configuration for this result is moved between nodes.
        /// </summary>
        private Dictionary<string, string>? _savedEnvironmentVariables;
 
        /// <summary>
        /// When this key is in the dictionary <see cref="_savedEnvironmentVariables"/>, serialize the build result version.
        /// </summary>
        private const string SpecialKeyForVersion = "=MSBUILDFEATUREBUILDRESULTHASVERSION=";
 
        /// <summary>
        /// Set of additional keys tat might be added to the dictionary <see cref="_savedEnvironmentVariables"/>.
        /// </summary>
        private static readonly HashSet<string> s_additionalEntriesKeys = new HashSet<string> { SpecialKeyForVersion };
 
        /// <summary>
        /// Snapshot of the current directory from the configuration this result comes from.
        /// This should only be populated when the configuration for this result is moved between nodes.
        /// </summary>
        private string? _savedCurrentDirectory;
 
        /// <summary>
        /// <see cref="ProjectInstance"/> state after the build. This is only provided if <see cref="BuildRequest.BuildRequestDataFlags"/>
        /// includes <see cref="BuildRequestDataFlags.ProvideProjectStateAfterBuild"/> or
        /// <see cref="BuildRequestDataFlags.ProvideSubsetOfStateAfterBuild"/> for the build request which this object is a result of,
        /// and will be <c>null</c> otherwise. Where available, it may be a non buildable-dummy object, and should only
        /// be used to retrieve <see cref="ProjectInstance.Properties"/>, <see cref="ProjectInstance.GlobalProperties"/> and
        /// <see cref="ProjectInstance.Items"/> from it. No other operation is guaranteed to be supported.
        /// </summary>
        private ProjectInstance? _projectStateAfterBuild;
 
        /// <summary>
        /// The flags provide additional control over the build results and may affect the cached value.
        /// </summary>
        /// <remarks>
        /// Is optional, the field is expected to be present starting <see cref="_version"/> 1.
        /// </remarks>
        private BuildRequestDataFlags _buildRequestDataFlags;
 
        private string? _schedulerInducedError;
 
        private HashSet<string>? _projectTargets;
 
        /// <summary>
        /// Constructor for serialization.
        /// </summary>
        public BuildResult()
        {
            _resultsByTarget = CreateTargetResultDictionary(1);
        }
 
        /// <summary>
        /// Constructor creates an empty build result
        /// </summary>
        /// <param name="request">The build request to which these results should be associated.</param>
        internal BuildResult(BuildRequest request)
            : this(request, null)
        {
        }
 
        /// <summary>
        /// Constructs a build result with an exception
        /// </summary>
        /// <param name="request">The build request to which these results should be associated.</param>
        /// <param name="exception">The exception, if any.</param>
        internal BuildResult(BuildRequest request, Exception? exception)
            : this(request, null, exception)
        {
        }
 
        /// <summary>
        /// Constructor creates a build result indicating a circular dependency was created.
        /// </summary>
        /// <param name="request">The build request to which these results should be associated.</param>
        /// <param name="circularDependency">Set to true if a circular dependency was detected.</param>
        internal BuildResult(BuildRequest request, bool circularDependency)
            : this(request, null)
        {
            _circularDependency = circularDependency;
        }
 
        /// <summary>
        /// Constructs a new build result based on existing results, but filtered by a specified set of target names
        /// </summary>
        /// <param name="existingResults">The existing results.</param>
        /// <param name="targetNames">The target names whose results we will take from the existing results, if they exist.</param>
        internal BuildResult(BuildResult existingResults, string[] targetNames)
        {
            _submissionId = existingResults._submissionId;
            _configurationId = existingResults._configurationId;
            _globalRequestId = existingResults._globalRequestId;
            _parentGlobalRequestId = existingResults._parentGlobalRequestId;
            _nodeRequestId = existingResults._nodeRequestId;
            _requestException = existingResults._requestException;
            _resultsByTarget = CreateTargetResultDictionaryWithContents(existingResults, targetNames);
            _baseOverallResult = existingResults.OverallResult == BuildResultCode.Success;
            _buildRequestDataFlags = existingResults._buildRequestDataFlags;
            _projectStateAfterBuild = existingResults._projectStateAfterBuild;
 
            _circularDependency = existingResults._circularDependency;
        }
 
        /// <summary>
        /// Constructs a new build result with existing results, but associated with the specified request.
        /// </summary>
        /// <param name="request">The build request with which these results should be associated.</param>
        /// <param name="existingResults">The existing results, if any.</param>
        /// <param name="exception">The exception, if any</param>
        internal BuildResult(BuildRequest request, BuildResult? existingResults, Exception? exception)
            : this(request, existingResults, null, exception)
        {
        }
 
        /// <summary>
        /// Constructs a new build result with existing results, but associated with the specified request.
        /// </summary>
        /// <param name="request">The build request with which these results should be associated.</param>
        /// <param name="existingResults">The existing results, if any.</param>
        /// <param name="targetNames">The list of target names that are the subset of results that should be returned.</param>
        /// <param name="exception">The exception, if any</param>
        internal BuildResult(BuildRequest request, BuildResult? existingResults, string[]? targetNames, Exception? exception)
        {
            _submissionId = request.SubmissionId;
            _configurationId = request.ConfigurationId;
            _globalRequestId = request.GlobalRequestId;
            _parentGlobalRequestId = request.ParentGlobalRequestId;
            _nodeRequestId = request.NodeRequestId;
            _circularDependency = false;
            _baseOverallResult = true;
            _buildRequestDataFlags = request.BuildRequestDataFlags;
 
            if (existingResults == null)
            {
                _requestException = exception;
                _resultsByTarget = CreateTargetResultDictionary(0);
            }
            else
            {
                _requestException = exception ?? existingResults._requestException;
                _resultsByTarget = targetNames == null ? existingResults._resultsByTarget : CreateTargetResultDictionaryWithContents(existingResults, targetNames);
                if (request.RequestedProjectState != null)
                {
                    _projectStateAfterBuild = existingResults._projectStateAfterBuild?.FilteredCopy(request.RequestedProjectState);
                }
            }
        }
 
        /// <summary>
        /// Constructor which allows reporting results for a different nodeRequestId
        /// </summary>
        internal BuildResult(BuildResult result, int nodeRequestId)
        {
            _configurationId = result._configurationId;
            _globalRequestId = result._globalRequestId;
            _parentGlobalRequestId = result._parentGlobalRequestId;
            _nodeRequestId = nodeRequestId;
            _requestException = result._requestException;
            _resultsByTarget = result._resultsByTarget;
            _circularDependency = result._circularDependency;
            _initialTargets = result._initialTargets;
            _defaultTargets = result._defaultTargets;
            _projectTargets = result._projectTargets;
            _baseOverallResult = result.OverallResult == BuildResultCode.Success;
        }
 
        internal BuildResult(BuildResult result, int submissionId, int configurationId, int requestId, int parentRequestId, int nodeRequestId)
        {
            _submissionId = submissionId;
            _configurationId = configurationId;
            _globalRequestId = requestId;
            _parentGlobalRequestId = parentRequestId;
            _nodeRequestId = nodeRequestId;
 
            _requestException = result._requestException;
            _resultsByTarget = result._resultsByTarget;
            _circularDependency = result._circularDependency;
            _initialTargets = result._initialTargets;
            _defaultTargets = result._defaultTargets;
            _projectTargets = result._projectTargets;
            _baseOverallResult = result.OverallResult == BuildResultCode.Success;
        }
 
        /// <summary>
        /// Constructor for deserialization
        /// </summary>
        private BuildResult(ITranslator translator)
        {
            ((ITranslatable)this).Translate(translator);
            _resultsByTarget ??= CreateTargetResultDictionary(1);
        }
 
        /// <summary>
        /// Returns the submission id.
        /// </summary>
        public override int SubmissionId
        {
            [DebuggerStepThrough]
            get
            { return _submissionId; }
        }
 
        /// <summary>
        /// Returns the configuration ID for this result.
        /// </summary>
        public int ConfigurationId
        {
            [DebuggerStepThrough]
            get
            { return _configurationId; }
        }
 
        /// <summary>
        /// Returns the build request id for which this result was generated
        /// </summary>
        public int GlobalRequestId
        {
            [DebuggerStepThrough]
            get
            { return _globalRequestId; }
        }
 
        /// <summary>
        /// Returns the build request id for the parent of the request for which this result was generated
        /// </summary>
        public int ParentGlobalRequestId
        {
            [DebuggerStepThrough]
            get
            { return _parentGlobalRequestId; }
        }
 
        /// <summary>
        /// Returns the node build request id for which this result was generated
        /// </summary>
        public int NodeRequestId
        {
            [DebuggerStepThrough]
            get
            { return _nodeRequestId; }
        }
 
        /// <summary>
        /// Returns the exception generated while this result was run, if any.
        /// </summary>
        public override Exception? Exception
        {
            [DebuggerStepThrough]
            get
            { return _requestException; }
 
            [DebuggerStepThrough]
            internal set
            { _requestException = value; }
        }
 
        /// <summary>
        /// Returns a flag indicating if a circular dependency was detected.
        /// </summary>
        public override bool CircularDependency
        {
            [DebuggerStepThrough]
            get
            { return _circularDependency; }
        }
 
        /// <summary>
        /// Returns the overall result for this result set.
        /// </summary>
        public override BuildResultCode OverallResult
        {
            get
            {
                if (_requestException != null || _circularDependency || !_baseOverallResult)
                {
                    return BuildResultCode.Failure;
                }
 
                foreach (KeyValuePair<string, TargetResult> result in _resultsByTarget ?? [])
                {
                    if ((result.Value.ResultCode == TargetResultCode.Failure && !result.Value.TargetFailureDoesntCauseBuildFailure)
                        || result.Value.AfterTargetsHaveFailed)
                    {
                        return BuildResultCode.Failure;
                    }
                }
 
                return BuildResultCode.Success;
            }
        }
 
        /// <summary>
        /// Returns an enumerator for all target results in this build result
        /// </summary>
        public IDictionary<string, TargetResult> ResultsByTarget
        {
            [DebuggerStepThrough]
            get
            { return _resultsByTarget; }
        }
 
        /// <summary>
        /// <see cref="ProjectInstance"/> state after the build. In general, it may be a non buildable-dummy object, and should only
        /// be used to retrieve <see cref="ProjectInstance.Properties"/>, <see cref="ProjectInstance.GlobalProperties"/> and
        /// <see cref="ProjectInstance.Items"/> from it. Any other operation is not guaranteed to be supported.
        /// </summary>
        public ProjectInstance? ProjectStateAfterBuild
        {
            get => _projectStateAfterBuild;
            set => _projectStateAfterBuild = value;
        }
 
        /// <summary>
        /// Gets the flags that were used in the build request to which these results are associated.
        /// See <see cref="Execution.BuildRequestDataFlags"/> for examples of the available flags.
        /// </summary>
        /// <remarks>
        /// Is optional, this property exists starting version 1.
        /// </remarks>
        public BuildRequestDataFlags? BuildRequestDataFlags => (_version > 0) ? _buildRequestDataFlags : null;
 
        /// <summary>
        /// Returns the node packet type.
        /// </summary>
        NodePacketType INodePacket.Type
        {
            [DebuggerStepThrough]
            get
            { return NodePacketType.BuildResult; }
        }
 
        /// <summary>
        /// Holds a snapshot of the environment at the time we blocked.
        /// </summary>
        Dictionary<string, string>? IBuildResults.SavedEnvironmentVariables
        {
            get => _savedEnvironmentVariables;
 
            set => _savedEnvironmentVariables = value;
        }
 
        /// <summary>
        /// Holds a snapshot of the current working directory at the time we blocked.
        /// </summary>
        string? IBuildResults.SavedCurrentDirectory
        {
            get => _savedCurrentDirectory;
 
            set => _savedCurrentDirectory = value;
        }
 
        /// <summary>
        /// Returns the initial targets for the configuration which requested these results.
        /// </summary>
        internal List<string>? InitialTargets
        {
            [DebuggerStepThrough]
            get
            { return _initialTargets; }
 
            [DebuggerStepThrough]
            set
            { _initialTargets = value; }
        }
 
        /// <summary>
        /// Returns the default targets for the configuration which requested these results.
        /// </summary>
        internal List<string>? DefaultTargets
        {
            [DebuggerStepThrough]
            get
            { return _defaultTargets; }
 
            [DebuggerStepThrough]
            set
            { _defaultTargets = value; }
        }
 
        /// <summary>
        /// The defined targets for the project associated with this build result.
        /// </summary>
        internal HashSet<string>? ProjectTargets
        {
            [DebuggerStepThrough]
            get => _projectTargets;
            [DebuggerStepThrough]
            set => _projectTargets = value;
        }
 
        /// <summary>
        /// Container used to transport errors from the scheduler (issued while computing a build result)
        /// to the TaskHost that has the proper logging context (project id, target id, task id, file location)
        /// </summary>
        internal string? SchedulerInducedError
        {
            get => _schedulerInducedError;
            set => _schedulerInducedError = value;
        }
 
        /// <summary>
        /// Indexer which sets or returns results for the specified target
        /// </summary>
        /// <param name="target">The target</param>
        /// <returns>The results for the specified target</returns>
        /// <exception>KeyNotFoundException is returned if the specified target doesn't exist when reading this property.</exception>
        /// <exception>ArgumentException is returned if the specified target already has results.</exception>
        public ITargetResult this[string target]
        {
            [DebuggerStepThrough]
            get
            { return _resultsByTarget![target]; }
        }
 
        /// <summary>
        /// Adds the results for the specified target to this result collection.
        /// </summary>
        /// <param name="target">The target to which these results apply.</param>
        /// <param name="result">The results for the target.</param>
        public void AddResultsForTarget(string target, TargetResult result)
        {
            ErrorUtilities.VerifyThrowArgumentNull(target);
            ErrorUtilities.VerifyThrowArgumentNull(result);
 
            lock (this)
            {
                _resultsByTarget ??= CreateTargetResultDictionary(1);
            }
 
            if (_resultsByTarget.TryGetValue(target, out TargetResult? targetResult))
            {
                ErrorUtilities.VerifyThrow(targetResult.ResultCode == TargetResultCode.Skipped, "Items already exist for target {0}.", target);
            }
 
            _resultsByTarget[target] = result;
        }
 
        /// <summary>
        /// Keep the results only for targets in <paramref name="targetsToKeep"/>.
        /// </summary>
        /// <param name="targetsToKeep">The targets whose results to keep.</param>
        internal void KeepSpecificTargetResults(IReadOnlyCollection<string> targetsToKeep)
        {
            ErrorUtilities.VerifyThrow(
                targetsToKeep.Count > 0,
                $"{nameof(targetsToKeep)} should contain at least one target.");
 
            foreach (string target in _resultsByTarget?.Keys ?? [])
            {
                if (!targetsToKeep.Contains(target))
                {
                    _ = _resultsByTarget!.TryRemove(target, out _);
                }
            }
        }
 
        /// <summary>
        /// Merges the specified results with the results contained herein.
        /// </summary>
        /// <param name="results">The results to merge in.</param>
        public void MergeResults(BuildResult results)
        {
            ErrorUtilities.VerifyThrowArgumentNull(results);
            ErrorUtilities.VerifyThrow(results.ConfigurationId == ConfigurationId, "Result configurations don't match");
 
            // If we are merging with ourself or with a shallow clone, do nothing.
            if (ReferenceEquals(this, results) || ReferenceEquals(_resultsByTarget, results._resultsByTarget))
            {
                return;
            }
 
            // Merge in the results
            foreach (KeyValuePair<string, TargetResult> targetResult in results._resultsByTarget ?? [])
            {
                // NOTE: I believe that because we only allow results for a given target to be produced and cached once for a given configuration,
                // we can never receive conflicting results for that target, since the cache and build request manager would always return the
                // cached results after the first time the target is built.  As such, we can allow "duplicates" to be merged in because there is
                // no change.  If, however, this turns out not to be the case, we need to re-evaluate this merging and possibly re-enable the
                // assertion below.
                // ErrorUtilities.VerifyThrow(!HasResultsForTarget(targetResult.Key), "Results already exist");
 
                // Copy the new results in.
                _resultsByTarget![targetResult.Key] = targetResult.Value;
            }
 
            // If there is an exception and we did not previously have one, add it in.
            _requestException ??= results.Exception;
        }
 
        /// <summary>
        /// Determines if there are any results for the specified target.
        /// </summary>
        /// <param name="target">The target for which results are desired.</param>
        /// <returns>True if results exist, false otherwise.</returns>
        public bool HasResultsForTarget(string target)
        {
            return _resultsByTarget?.ContainsKey(target) ?? false;
        }
 
        #region INodePacket Members
 
        /// <summary>
        /// Reads or writes the packet to the serializer.
        /// </summary>
        void ITranslatable.Translate(ITranslator translator)
        {
            translator.Translate(ref _submissionId);
            translator.Translate(ref _configurationId);
            translator.Translate(ref _globalRequestId);
            translator.Translate(ref _parentGlobalRequestId);
            translator.Translate(ref _nodeRequestId);
            translator.Translate(ref _initialTargets);
            translator.Translate(ref _defaultTargets);
            translator.Translate(ref _projectTargets);
            translator.Translate(ref _circularDependency);
            translator.TranslateException(ref _requestException);
            translator.TranslateDictionary(ref _resultsByTarget, TargetResult.FactoryForDeserialization, CreateTargetResultDictionary);
            translator.Translate(ref _baseOverallResult);
            translator.Translate(ref _projectStateAfterBuild, ProjectInstance.FactoryForDeserialization);
            translator.Translate(ref _savedCurrentDirectory);
            translator.Translate(ref _schedulerInducedError);
 
            // This is a work-around for the bug https://github.com/dotnet/msbuild/issues/10208
            // We are adding a version field to this class to make the ResultsCache backwards compatible with at least 2 previous releases.
            // The adding of a version field is done without a breaking change in 3 steps, each separated with at least 1 intermediate release.
            //
            // 1st step (done): Add a special key to the _savedEnvironmentVariables dictionary during the serialization. A workaround overload of the TranslateDictionary function is created to achieve it.
            // The presence of this key will indicate that the version is serialized next.
            // When serializing, add a key to the dictionary and serialize a version field.
            // Do not actually save the special key to dictionary during the deserialization, but read a version as a next field if it presents.
            //
            // 2nd step: Stop serialize a special key with the dictionary _savedEnvironmentVariables using the TranslateDictionary function workaround overload. Always serialize and de-serialize the version field.
            // Continue to deserialize _savedEnvironmentVariables with the TranslateDictionary function workaround overload in order not to deserialize dictionary with the special keys.
            //
            // 3rd step: Stop using the TranslateDictionary function workaround overload during _savedEnvironmentVariables deserialization.
            if (_version == 0)
            {
                // Escape hatch: serialize/deserialize without version field.
                translator.TranslateDictionary(ref _savedEnvironmentVariables, StringComparer.OrdinalIgnoreCase);
            }
            else
            {
                Dictionary<string, string> additionalEntries = new();
 
                if (translator.Mode == TranslationDirection.WriteToStream)
                {
                    // Add the special key SpecialKeyForVersion to additional entries indicating the presence of a version to the _savedEnvironmentVariables dictionary.
                    additionalEntries.Add(SpecialKeyForVersion, String.Empty);
 
                    // Serialize the special key together with _savedEnvironmentVariables dictionary using the workaround overload of TranslateDictionary:
                    translator.TranslateDictionary(ref _savedEnvironmentVariables, StringComparer.OrdinalIgnoreCase, ref additionalEntries, s_additionalEntriesKeys);
 
                    // Serialize version
                    translator.Translate(ref _version);
                }
                else if (translator.Mode == TranslationDirection.ReadFromStream)
                {
                    // Read the dictionary using the workaround overload of TranslateDictionary: special keys (additionalEntriesKeys) would be read to additionalEntries instead of the _savedEnvironmentVariables dictionary.
                    translator.TranslateDictionary(ref _savedEnvironmentVariables, StringComparer.OrdinalIgnoreCase, ref additionalEntries, s_additionalEntriesKeys);
 
                    // If the special key SpecialKeyForVersion present in additionalEntries, also read a version, otherwise set it to 0.
                    if (additionalEntries is not null && additionalEntries.ContainsKey(SpecialKeyForVersion))
                    {
                        translator.Translate(ref _version);
                    }
                    else
                    {
                        _version = 0;
                    }
                }
            }
 
            // Starting version 1 this _buildRequestDataFlags field is present.
            if (_version > 0)
            {
                translator.TranslateEnum(ref _buildRequestDataFlags, (int)_buildRequestDataFlags);
            }
        }
 
        /// <summary>
        /// Factory for serialization
        /// </summary>
        internal static BuildResult FactoryForDeserialization(ITranslator translator)
        {
            return new BuildResult(translator);
        }
 
        #endregion
 
        /// <summary>
        /// Caches all of the targets results we can.
        /// </summary>
        internal void CacheIfPossible()
        {
            foreach (KeyValuePair<string, TargetResult> targetResultPair in _resultsByTarget ?? [])
            {
                targetResultPair.Value.CacheItems(ConfigurationId, targetResultPair.Key);
            }
        }
 
        /// <summary>
        /// Clear cached files from disk.
        /// </summary>
        internal void ClearCachedFiles()
        {
            string resultsDirectory = TargetResult.GetCacheDirectory(_configurationId, "None" /*Does not matter because we just need the directory name not the file*/);
            if (FileSystems.Default.DirectoryExists(resultsDirectory))
            {
                FileUtilities.DeleteDirectoryNoThrow(resultsDirectory, true /*recursive*/);
            }
        }
 
        /// <summary>
        /// Clones the build result (the resultsByTarget field is only a shallow copy).
        /// </summary>
        internal BuildResult Clone()
        {
            BuildResult result = new BuildResult
            {
                _submissionId = _submissionId,
                _configurationId = _configurationId,
                _globalRequestId = _globalRequestId,
                _parentGlobalRequestId = _parentGlobalRequestId,
                _nodeRequestId = _nodeRequestId,
                _requestException = _requestException,
                _resultsByTarget = new ConcurrentDictionary<string, TargetResult>(_resultsByTarget, StringComparer.OrdinalIgnoreCase),
                _baseOverallResult = OverallResult == BuildResultCode.Success,
                _circularDependency = _circularDependency
            };
 
            return result;
        }
 
        /// <summary>
        /// Sets the overall result.
        /// </summary>
        /// <param name="overallResult"><code>true</code> if the result is success, otherwise <code>false</code>.</param>
        internal void SetOverallResult(bool overallResult)
        {
            _baseOverallResult = false;
        }
 
        /// <summary>
        /// Creates the target result dictionary.
        /// </summary>
        private static ConcurrentDictionary<string, TargetResult> CreateTargetResultDictionary(int capacity)
        {
            return new ConcurrentDictionary<string, TargetResult>(1, capacity, StringComparer.OrdinalIgnoreCase);
        }
 
        /// <summary>
        /// Creates the target result dictionary and populates it with however many target results are
        /// available given the list of targets passed.
        /// </summary>
        private static ConcurrentDictionary<string, TargetResult> CreateTargetResultDictionaryWithContents(BuildResult existingResults, string[] targetNames)
        {
            ConcurrentDictionary<string, TargetResult> resultsByTarget = CreateTargetResultDictionary(targetNames.Length);
 
            foreach (string target in targetNames)
            {
                if (existingResults.ResultsByTarget?.TryGetValue(target, out TargetResult? targetResult) ?? false)
                {
                    resultsByTarget[target] = targetResult;
                }
            }
 
            return resultsByTarget;
        }
    }
}