File: RestoreCommand\RestoreTargetGraph.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Commands\NuGet.Commands.csproj (NuGet.Commands)
// 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.

#nullable disable

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using NuGet.Client;
using NuGet.DependencyResolver;
using NuGet.Frameworks;
using NuGet.LibraryModel;
using NuGet.RuntimeModel;

namespace NuGet.Commands
{
    [DebuggerDisplay("{TargetAlias} {Framework} {RuntimeIdentifier}")]
    public class RestoreTargetGraph : IRestoreTargetGraph
    {
        /// <summary>
        /// Gets the runtime identifier used during the restore operation on this graph
        /// </summary>
        public string RuntimeIdentifier { get; }

        /// <summary>
        /// Gets the <see cref="NuGetFramework" /> used during the restore operation on this graph
        /// </summary>
        public NuGetFramework Framework { get; }

        public string TargetAlias { get; }

        /// <summary>
        /// Gets the <see cref="ManagedCodeConventions" /> used to resolve assets from packages in this graph
        /// </summary>
        public ManagedCodeConventions Conventions { get; }

        /// <summary>
        /// Gets the <see cref="RuntimeGraph" /> that defines runtimes and their relationships for this graph
        /// </summary>
        public RuntimeGraph RuntimeGraph { get; }

        /// <summary>
        /// Gets the resolved dependency graph
        /// </summary>
        public IEnumerable<GraphNode<RemoteResolveResult>> Graphs { get; }

        public ISet<RemoteMatch> Install { get; }

        public ISet<GraphItem<RemoteResolveResult>> Flattened { get; }

        public ISet<LibraryRange> Unresolved { get; }

        public bool InConflict { get; }

        public string TargetGraphName { get; }

        // TODO: Move conflicts to AnalyzeResult
        public IEnumerable<ResolverConflict> Conflicts { get; }

        public AnalyzeResult<RemoteResolveResult> AnalyzeResult { get; }

        public ISet<ResolvedDependencyKey> ResolvedDependencies { get; }

        internal RestoreTargetGraph(IEnumerable<ResolverConflict> conflicts,
                                   string targetAlias,
                                   NuGetFramework framework,
                                   string runtimeIdentifier,
                                   RuntimeGraph runtimeGraph,
                                   IEnumerable<GraphNode<RemoteResolveResult>> graphs,
                                   ISet<RemoteMatch> install,
                                   ISet<GraphItem<RemoteResolveResult>> flattened,
                                   ISet<LibraryRange> unresolved,
                                   AnalyzeResult<RemoteResolveResult> analyzeResult,
                                   ISet<ResolvedDependencyKey> resolvedDependencies)
        {
            Conflicts = conflicts.ToArray();
            RuntimeIdentifier = runtimeIdentifier;
            TargetAlias = targetAlias;
            RuntimeGraph = runtimeGraph;
            Framework = framework;
            Graphs = graphs;
            TargetGraphName = string.IsNullOrEmpty(TargetAlias) ? FrameworkRuntimePair.GetTargetGraphName(Framework, RuntimeIdentifier) : GetTargetGraphName(TargetAlias, RuntimeIdentifier);
            Conventions = new ManagedCodeConventions(runtimeGraph);

            Install = install;
            Flattened = flattened;
            AnalyzeResult = analyzeResult;
            Unresolved = unresolved;
            ResolvedDependencies = resolvedDependencies;
        }

        internal static string GetTargetGraphName(string targetAlias, string runtimeIdentifier)
        {
            if (string.IsNullOrEmpty(targetAlias)) throw new ArgumentNullException(nameof(targetAlias));

            if (string.IsNullOrEmpty(runtimeIdentifier))
            {
                return targetAlias;
            }
            else
            {
                return string.Format(
                    CultureInfo.InvariantCulture,
                    "{0}/{1}",
                    targetAlias,
                    runtimeIdentifier);
            }
        }

        internal static RestoreTargetGraph Create(
            RuntimeGraph runtimeGraph,
            IEnumerable<GraphNode<RemoteResolveResult>> graphs,
            RemoteWalkContext context,
            string targetAlias,
            NuGetFramework framework,
            string runtimeIdentifier)
        {
            var install = new HashSet<RemoteMatch>();
            var flattened = new HashSet<GraphItem<RemoteResolveResult>>();
            var unresolved = new HashSet<LibraryRange>();

            var conflicts = new Dictionary<string, HashSet<ResolverRequest>>();
            var analyzeResult = new AnalyzeResult<RemoteResolveResult>();
            var resolvedDependencies = new HashSet<ResolvedDependencyKey>();

            foreach (var graph in graphs)
            {
                var result = graph.Analyze();

                analyzeResult.Combine(result);
            }

            graphs.ForEach(node =>
                {
                    if (node == null || node.Key == null)
                    {
                        return;
                    }

                    if (node.Disposition != Disposition.Rejected)
                    {
                        if (node.Disposition == Disposition.Acceptable)
                        {
                            // This wasn't resolved. It's a conflict.
                            HashSet<ResolverRequest> ranges;
                            if (!conflicts.TryGetValue(node.Key.Name, out ranges))
                            {
                                ranges = new HashSet<ResolverRequest>();
                                conflicts[node.Key.Name] = ranges;
                            }

                            // OuterNode may be null if the project itself conflicts with a package name
                            var requestor = node.OuterNode == null ? node.Item.Key : node.OuterNode.Item.Key;

                            ranges.Add(new ResolverRequest(requestor, node.Key));
                        }

                        if (node?.Item?.Key?.Type == LibraryType.Unresolved)
                        {
                            if (node.Key.VersionRange != null)
                            {
                                unresolved.Add(node.Key);
                            }

                            return;
                        }

                        // Don't add rejected nodes since we only want to write reduced nodes
                        // to the lock file
                        flattened.Add(node.Item);
                    }

                    if (node?.OuterNode != null && node.Item.Key.Type != LibraryType.Unresolved)
                    {
                        var dependencyKey = new ResolvedDependencyKey(
                            parent: node.OuterNode.Item.Key,
                            range: node.Key.VersionRange,
                            child: node.Item.Key);

                        resolvedDependencies.Add(dependencyKey);
                    }

                    // If the package came from a remote library provider, it needs to be installed locally
                    // Rejected nodes are included here to avoid downloading them from remote sources
                    // each time the lock file is generated.
                    var isRemote = context.RemoteLibraryProviders.Contains(node.Item.Data.Match.Provider);
                    if (isRemote)
                    {
                        install.Add(node.Item.Data.Match);
                    }
                });

            return new RestoreTargetGraph(
                conflicts.Select(p => new ResolverConflict(p.Key, p.Value)),
                targetAlias,
                framework,
                runtimeIdentifier,
                runtimeGraph,
                graphs,
                install,
                flattened,
                unresolved,
                analyzeResult,
                resolvedDependencies);
        }
    }
}