private NuGetFramework? GetNearestInternal()

in src/NuGet.Core/NuGet.Frameworks/FrameworkReducer.cs [69:226]


        private NuGetFramework? GetNearestInternal(NuGetFramework framework, IEnumerable<NuGetFramework> possibleFrameworks)
        {
            NuGetFramework? nearest = null;

            // Unsupported frameworks always lose, throw them out unless it's all we were given
            if (possibleFrameworks.Any(e => e != NuGetFramework.UnsupportedFramework))
            {
                possibleFrameworks = possibleFrameworks.Where(e => e != NuGetFramework.UnsupportedFramework);
            }

            // Try exact matches first
            nearest = possibleFrameworks.FirstOrDefault(f => NuGetFrameworkFullComparer.Instance.Equals(framework, f));

            if (nearest == null)
            {
                // Elimate non-compatible frameworks
                var compatible = possibleFrameworks.Where(f => _compat.IsCompatible(framework, f));

                // Remove lower versions of compatible frameworks
                var reduced = ReduceUpwards(compatible);

                bool isNet6Era = framework.IsNet5Era && framework.Version.Major >= 6;

                // Reduce to the same framework name if possible, with an exception for Xamarin, MonoAndroid and Tizen when net6.0+
                if (reduced.Count() > 1 && reduced.Any(f => NuGetFrameworkNameComparer.Instance.Equals(f, framework)))
                {
                    reduced = reduced.Where(f =>
                    {
                        if (isNet6Era && framework.HasPlatform && (
                            f.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.MonoAndroid, StringComparison.OrdinalIgnoreCase)
                            || f.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.Tizen, StringComparison.OrdinalIgnoreCase)
                            ))
                        {
                            return true;
                        }
                        else
                        {
                            return NuGetFrameworkNameComparer.Instance.Equals(f, framework);
                        }
                    });
                }

                // PCL reduce
                if (reduced.Count() > 1)
                {
                    // if we have a pcl and non-pcl mix, throw out the pcls
                    if (reduced.Any(f => f.IsPCL)
                        && reduced.Any(f => !f.IsPCL))
                    {
                        reduced = reduced.Where(f => !f.IsPCL);
                    }
                    else if (reduced.All(f => f.IsPCL))
                    {
                        // decide between PCLs
                        if (framework.IsPCL)
                        {
                            reduced = GetNearestPCLtoPCL(framework, reduced);
                        }
                        else
                        {
                            reduced = GetNearestNonPCLtoPCL(framework, reduced);
                        }

                        if (reduced.Count() > 1)
                        {
                            // For scenarios where we are unable to decide between PCLs, choose the PCL with the
                            // least frameworks. Less frameworks means less compatibility which means it is nearer to the target.
                            reduced = new NuGetFramework[] { GetBestPCL(reduced)! };
                        }
                    }
                }

                // Packages based framework reduce, only if the project is not packages based
                if (reduced.Count() > 1
                    && !framework.IsPackageBased
                    && reduced.Any(f => f.IsPackageBased)
                    && reduced.Any(f => !f.IsPackageBased))
                {
                    // If we have a packages based and non-packages based mix, throw out the packages based frameworks.
                    // This situation is unlikely but it could happen with framework mappings that do not provide
                    // a relationship between the frameworks and the compatible packages based frameworks.
                    // Ex: net46, dotnet -> net46
                    reduced = reduced.Where(f => !f.IsPackageBased);
                }

                // Profile reduce
                if (reduced.Count() > 1
                    && !reduced.Any(f => f.IsPCL))
                {
                    // Prefer the same framework and profile
                    if (framework.HasProfile)
                    {
                        var sameProfile = reduced.Where(f => NuGetFrameworkNameComparer.Instance.Equals(framework, f)
                                                             && StringComparer.OrdinalIgnoreCase.Equals(framework.Profile, f.Profile));

                        if (sameProfile.Any())
                        {
                            reduced = sameProfile;
                        }
                    }

                    // Prefer frameworks without profiles
                    if (reduced.Count() > 1
                        && reduced.Any(f => f.HasProfile)
                        && reduced.Any(f => !f.HasProfile))
                    {
                        reduced = reduced.Where(f => !f.HasProfile);
                    }
                }

                // Platforms reduce
                if (reduced.Count() > 1
                    && framework.HasPlatform)
                {
                    if (!isNet6Era || reduced.Any(f => NuGetFrameworkNameComparer.Instance.Equals(framework, f) && f.Version.Major >= 6))
                    {
                        // Prefer the highest framework version, likely to be the non-platform specific option.
                        reduced = reduced.Where(f => NuGetFrameworkNameComparer.Instance.Equals(framework, f)).GroupBy(f => f.Version).OrderByDescending(f => f.Key).First();
                    }
                    else if (isNet6Era && reduced.Any(f =>
                    {
                        return f.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.MonoAndroid, StringComparison.OrdinalIgnoreCase)
                        || f.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.Tizen, StringComparison.OrdinalIgnoreCase);
                    }))
                    {
                        // We have a special case for *some* Xamarin-related frameworks here. For specific precedence rules, please see:
                        // https://github.com/dotnet/designs/blob/main/accepted/2021/net6.0-tfms/net6.0-tfms.md#compatibility-rules
                        reduced = reduced.GroupBy(f => f.Framework).OrderByDescending(f => f.Key).First(f =>
                        {
                            NuGetFramework first = f.First();
                            return first.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.MonoAndroid, StringComparison.OrdinalIgnoreCase)
                                || first.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.Tizen, StringComparison.OrdinalIgnoreCase);
                        });
                    }
                }

                // if we have reduced down to a single framework, use that
                if (reduced.Count() == 1)
                {
                    nearest = reduced.Single();
                }

                // this should be a very rare occurrence
                // at this point we are unable to decide between the remaining frameworks in any useful way
                // just take the first one by rev alphabetical order if we can't narrow it down at all
                if (nearest == null && reduced.Any())
                {
                    // Sort by precedence rules, then by name in the case of a tie
                    nearest = reduced
                        .OrderBy(f => f, new FrameworkPrecedenceSorter(_mappings, false))
                        .ThenByDescending(f => f, NuGetFrameworkSorter.Instance)
                        .ThenBy(f => f.GetHashCode())
                        .First();
                }
            }

            return nearest;
        }