File: Evaluation\LazyItemEvaluator.RemoveOperation.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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.Build.Construction;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
 
#nullable disable
 
namespace Microsoft.Build.Evaluation
{
    internal partial class LazyItemEvaluator<P, I, M, D>
    {
        private class RemoveOperation : LazyItemOperation
        {
            private readonly ImmutableList<string> _matchOnMetadata;
            private MetadataTrie<P, I> _metadataSet;
 
            public RemoveOperation(RemoveOperationBuilder builder, LazyItemEvaluator<P, I, M, D> lazyEvaluator)
                : base(builder, lazyEvaluator)
            {
                _matchOnMetadata = builder.MatchOnMetadata.ToImmutable();
 
                ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(
                    _matchOnMetadata.IsEmpty || _itemSpec.Fragments.All(f => f is ItemSpec<P, I>.ItemExpressionFragment),
                    new BuildEventFileInfo(string.Empty),
                    "OM_MatchOnMetadataIsRestrictedToReferencedItems");
 
                if (_matchOnMetadata.Any())
                {
                    _metadataSet = new MetadataTrie<P, I>(builder.MatchOnMetadataOptions, _matchOnMetadata, _itemSpec);
                }
            }
 
            /// <summary>
            /// Apply the Remove operation.
            /// </summary>
            /// <remarks>
            /// This override exists to apply the removing-everything short-circuit and to avoid creating a redundant list of items to remove.
            /// </remarks>
            protected override void ApplyImpl(OrderedItemDataCollection.Builder listBuilder, ImmutableHashSet<string> globsToIgnore)
            {
                if (!_conditionResult)
                {
                    return;
                }
 
                bool matchingOnMetadata = _matchOnMetadata.Any();
                if (!matchingOnMetadata)
                {
                    if (ItemspecContainsASingleBareItemReference(_itemSpec, _itemElement.ItemType))
                    {
                        // Perf optimization: If the Remove operation references itself (e.g. <I Remove="@(I)"/>)
                        // then all items are removed and matching is not necessary
                        listBuilder.Clear();
                        return;
                    }
 
                    if (listBuilder.Count >= Traits.Instance.DictionaryBasedItemRemoveThreshold)
                    {
                        // Perf optimization: If the number of items in the running list is large, construct a dictionary,
                        // enumerate all items referenced by the item spec, and perform dictionary look-ups to find items
                        // to remove.
                        IList<string> matches = _itemSpec.IntersectsWith(listBuilder.Dictionary);
                        listBuilder.RemoveAll(matches);
                        return;
                    }
                }
 
                // todo Perf: do not match against the globs: https://github.com/dotnet/msbuild/issues/2329
                HashSet<I> items = null;
                foreach (ItemData item in listBuilder)
                {
                    bool isMatch = matchingOnMetadata ? MatchesItemOnMetadata(item.Item) : _itemSpec.MatchesItem(item.Item);
                    if (isMatch)
                    {
                        items ??= new HashSet<I>();
                        items.Add(item.Item);
                    }
                }
                if (items is not null)
                {
                    listBuilder.RemoveAll(items);
                }
            }
 
            private bool MatchesItemOnMetadata(I item)
            {
                return _metadataSet.Contains(_matchOnMetadata.Select(m => item.GetMetadataValue(m)));
            }
 
            public ImmutableHashSet<string>.Builder GetRemovedGlobs()
            {
                var builder = ImmutableHashSet.CreateBuilder<string>();
 
                if (!_conditionResult)
                {
                    return builder;
                }
 
                var globs = _itemSpec.Fragments.OfType<GlobFragment>().Select(g => g.TextFragment);
 
                builder.UnionWith(globs);
 
                return builder;
            }
        }
 
        private class RemoveOperationBuilder : OperationBuilder
        {
            public ImmutableList<string>.Builder MatchOnMetadata { get; } = ImmutableList.CreateBuilder<string>();
 
            public MatchOnMetadataOptions MatchOnMetadataOptions { get; set; }
 
            public RemoveOperationBuilder(ProjectItemElement itemElement, bool conditionResult) : base(itemElement, conditionResult)
            {
            }
        }
    }
}