File: Remote\RemoteWalkContext.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.DependencyResolver.Core\NuGet.DependencyResolver.Core.csproj (NuGet.DependencyResolver.Core)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using NuGet.Common;
using NuGet.Configuration;
using NuGet.LibraryModel;
using NuGet.Protocol.Core.Types;

namespace NuGet.DependencyResolver
{
    public class RemoteWalkContext
    {
        public RemoteWalkContext(SourceCacheContext cacheContext, PackageSourceMapping packageSourceMapping, ILogger logger)
        {
            CacheContext = cacheContext ?? throw new ArgumentNullException(nameof(cacheContext));
            Logger = logger ?? throw new ArgumentNullException(nameof(logger));

            ProjectLibraryProviders = new List<IDependencyProvider>();
            LocalLibraryProviders = new List<IRemoteDependencyProvider>();
            RemoteLibraryProviders = new List<IRemoteDependencyProvider>();
            PackageSourceMapping = packageSourceMapping ?? throw new ArgumentNullException(nameof(packageSourceMapping));

            FindLibraryEntryCache = new TaskResultCache<LibraryRangeCacheKey, GraphItem<RemoteResolveResult>>();
            ResolvePackageLibraryMatchCache = new TaskResultCache<LibraryRange, Tuple<LibraryRange, RemoteMatch>>();

            LockFileLibraries = new Dictionary<LockFileCacheKey, IList<LibraryIdentity>>();
        }

        public SourceCacheContext CacheContext { get; }
        public ILogger Logger { get; }
        public IList<IDependencyProvider> ProjectLibraryProviders { get; }
        public IList<IRemoteDependencyProvider> LocalLibraryProviders { get; }
        public IList<IRemoteDependencyProvider> RemoteLibraryProviders { get; }
        public PackageSourceMapping PackageSourceMapping { get; }

        /// <summary>
        /// Packages lock file libraries to be used while generating restore graph.
        /// </summary>
        public IDictionary<LockFileCacheKey, IList<LibraryIdentity>> LockFileLibraries { get; }

        /// <summary>
        /// Library entry cache.
        /// </summary>
        internal TaskResultCache<LibraryRangeCacheKey, GraphItem<RemoteResolveResult>> FindLibraryEntryCache { get; }

        internal TaskResultCache<LibraryRange, Tuple<LibraryRange, RemoteMatch>> ResolvePackageLibraryMatchCache { get; }

        /// <summary>
        /// True if this is a csproj or similar project. Xproj should be false.
        /// </summary>
        public bool IsMsBuildBased { get; set; }

        /// <summary>
        /// Applies source mapping pattern filtering for a given package
        /// </summary>
        /// <param name="libraryRange"></param>
        /// <returns>Returns a subset of sources when source mapping patterns are configured otherwise returns all the sources</returns>
        public IList<IRemoteDependencyProvider> FilterDependencyProvidersForLibrary(LibraryRange libraryRange)
        {
            if (libraryRange == default)
                throw new ArgumentNullException(nameof(libraryRange));

            // filter package patterns if enabled
            if (PackageSourceMapping?.IsEnabled == true && libraryRange.TypeConstraintAllows(LibraryDependencyTarget.Package))
            {
                IReadOnlyList<string> sources = PackageSourceMapping.GetConfiguredPackageSources(libraryRange.Name);

                if (sources.Count == 0)
                {
                    return Array.Empty<IRemoteDependencyProvider>();
                }

                // PERF: Avoid Linq in hot paths.
                var filteredLibraryProviders = new List<IRemoteDependencyProvider>(sources.Count);
                for (int i = 0; i < RemoteLibraryProviders.Count; ++i)
                {
                    var current = RemoteLibraryProviders[i];
                    for (int j = 0; j < sources.Count; ++j)
                    {
                        if (StringComparer.OrdinalIgnoreCase.Equals(sources[j], current.Source.Name))
                        {
                            filteredLibraryProviders.Add(current);
                            break;
                        }
                    }
                }
                return filteredLibraryProviders;
            }
            return RemoteLibraryProviders;
        }

        /// <summary>
        /// Returns a list of unresolved remote matches.
        /// </summary>
        /// <remarks>
        /// The <see cref="FindLibraryEntryCache" /> is internal but the dependency resolver needs to know what packages were unresolved after walking the dependency graph.
        /// </remarks>
        /// <returns>A <see cref="HashSet{T}" /> containing the <see cref="RemoteMatch" /> objects representing unresolved packages.</returns>
        public async Task<HashSet<RemoteMatch>> GetUnresolvedRemoteMatchesAsync()
        {
            HashSet<RemoteMatch> packagesToInstall = new();

            foreach (LibraryRangeCacheKey key in FindLibraryEntryCache.Keys.NoAllocEnumerate())
            {
                if (!FindLibraryEntryCache.TryGetValue(key, out Task<GraphItem<RemoteResolveResult>>? task))
                {
                    continue;
                }

                GraphItem<RemoteResolveResult> item = await task;

                if (item.Key.Type == LibraryType.Unresolved || !RemoteLibraryProviders.Contains(item.Data.Match.Provider))
                {
                    continue;
                }

                packagesToInstall.Add(item.Data.Match);
            }

            return packagesToInstall;
        }
    }
}