File: BackEnd\BuildManager\CacheAggregator.cs
Web Access
Project: src\msbuild\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.Generic;
using System.Linq;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;

#nullable disable

namespace Microsoft.Build.Execution
{
    internal class CacheAggregator
    {
        private readonly Func<int> _nextConfigurationId;
        private readonly List<(IConfigCache ConfigCache, IResultsCache ResultsCache)> _inputCaches = new List<(IConfigCache ConfigCache, IResultsCache ResultsCache)>();
        private int _lastConfigurationId;
        private bool _aggregated;

        private ConfigCache _aggregatedConfigCache;
        private ResultsCache _aggregatedResultsCache;

        public CacheAggregator(Func<int> nextConfigurationId)
        {
            _nextConfigurationId = nextConfigurationId;
        }

        public void Add(IConfigCache configCache, IResultsCache resultsCache)
        {
            ErrorUtilities.VerifyThrowInternalNull(configCache);
            ErrorUtilities.VerifyThrowInternalNull(resultsCache);
            ErrorUtilities.VerifyThrow(!_aggregated, "Cannot add after aggregation");

            _inputCaches.Add((configCache, resultsCache));
        }

        public CacheAggregation Aggregate()
        {
            ErrorUtilities.VerifyThrow(!_aggregated, "Cannot aggregate twice");

            _aggregated = true;

            _aggregatedConfigCache = new ConfigCache();
            _aggregatedResultsCache = new ResultsCache();

            foreach (var (configCache, resultsCache) in _inputCaches)
            {
                InsertCaches(configCache, resultsCache);
            }

            return new CacheAggregation(_aggregatedConfigCache, _aggregatedResultsCache, _lastConfigurationId);
        }

        private void InsertCaches(IConfigCache configCache, IResultsCache resultsCache)
        {
            var configs = configCache.ToArray();
            var results = resultsCache.ToArray();

            ErrorUtilities.VerifyThrow(configs.Length == results.Length, "Assuming 1-to-1 mapping between configs and results. Otherwise it means the caches are either not minimal or incomplete");

            if (configs.Length == 0 && results.Length == 0)
            {
                return;
            }

            var seenConfigIds = new HashSet<int>();
            var configIdMapping = new Dictionary<int, int>();

            foreach (var config in configs)
            {
                seenConfigIds.Add(config.ConfigurationId);

                ErrorUtilities.VerifyThrow(_aggregatedConfigCache.GetMatchingConfiguration(config) == null, "Input caches should not contain entries for the same configuration");

                _lastConfigurationId = _nextConfigurationId();
                configIdMapping[config.ConfigurationId] = _lastConfigurationId;

                var newConfig = config.ShallowCloneWithNewId(_lastConfigurationId);
                newConfig.ResultsNodeId = Scheduler.InvalidNodeId;

                _aggregatedConfigCache.AddConfiguration(newConfig);
            }

            foreach (var result in results)
            {
                ErrorUtilities.VerifyThrow(seenConfigIds.Contains(result.ConfigurationId), "Each result should have a corresponding configuration. Otherwise the caches are not consistent");

                _aggregatedResultsCache.AddResult(
                    new BuildResult(
                        result,
                        BuildEventContext.InvalidSubmissionId,
                        configIdMapping[result.ConfigurationId],
                        BuildRequest.InvalidGlobalRequestId,
                        BuildRequest.InvalidGlobalRequestId,
                        BuildRequest.InvalidNodeRequestId));
            }
        }
    }

    internal class CacheAggregation
    {
        public CacheAggregation(IConfigCache configCache, IResultsCache resultsCache, int lastConfigurationId)
        {
            ConfigCache = configCache;
            ResultsCache = resultsCache;
            LastConfigurationId = lastConfigurationId;
        }

        public IConfigCache ConfigCache { get; }
        public IResultsCache ResultsCache { get; }
        public int LastConfigurationId { get; }
    }
}