File: PackageSpecExtensions.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.ProjectModel\NuGet.ProjectModel.csproj (NuGet.ProjectModel)
// 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.Linq;
using NuGet.Frameworks;

namespace NuGet.ProjectModel
{
    public static class PackageSpecExtensions
    {
        /// <summary>
        /// Get the nearest framework available in the project.
        /// </summary>
        public static TargetFrameworkInformation GetTargetFramework(this PackageSpec project, NuGetFramework targetFramework)
        {
            var frameworkInfo = project.TargetFrameworks.FirstOrDefault(f => NuGetFramework.Comparer.Equals(targetFramework, f.FrameworkName));
            if (frameworkInfo == null)
            {
                frameworkInfo = NuGetFrameworkUtility.GetNearest(project.TargetFrameworks,
                    targetFramework,
                    item => item.FrameworkName);
            }

            return frameworkInfo ?? new TargetFrameworkInformation();
        }

        /// <summary>
        /// Get restore metadata framework. This is based on the project's target frameworks, then an 
        /// exact match is found under restore metadata.
        /// </summary>
        public static ProjectRestoreMetadataFrameworkInfo GetRestoreMetadataFramework(this PackageSpec project, NuGetFramework targetFramework)
        {
            ProjectRestoreMetadataFrameworkInfo? frameworkInfo = null;

            var projectFrameworkInfo = GetTargetFramework(project, targetFramework);

            if (projectFrameworkInfo.FrameworkName != null)
            {
                frameworkInfo = project.RestoreMetadata?.TargetFrameworks
                    .FirstOrDefault(f => f.FrameworkName.Equals(projectFrameworkInfo.FrameworkName));
            }

            return frameworkInfo ?? new ProjectRestoreMetadataFrameworkInfo();
        }

        /// <summary>
        /// Get the <see cref="TargetFrameworkInformation"/> for the given target alias.
        /// If there is not a match, null is returned.
        /// </summary>
        /// <param name="project">The project</param>
        /// <param name="targetAlias">The target alias to search for</param>
        /// <returns>The <see cref="TargetFrameworkInformation"/> if it exists, <see langword="null"/> otherwise. </returns>
        public static TargetFrameworkInformation? GetTargetFramework(this PackageSpec project, string? targetAlias)
        {
            foreach (var framework in project.TargetFrameworks.NoAllocEnumerate())
            {
                if (string.Equals(framework.TargetAlias, targetAlias, StringComparison.OrdinalIgnoreCase))
                {
                    return framework;
                }
            }
            return null;
        }

        /// <summary>
        /// Get the <see cref="ProjectRestoreMetadataFrameworkInfo"/> for the given target alias.
        /// If there is not a match, null is returned.
        /// </summary>
        /// <param name="project">The project</param>
        /// <param name="targetAlias">The target alias to search for</param>
        /// <returns>The <see cref="ProjectRestoreMetadataFrameworkInfo"/> if it exists, <see langword="null"/> otherwise. </returns>
        public static ProjectRestoreMetadataFrameworkInfo? GetRestoreMetadataFramework(this PackageSpec project, string? targetAlias)
        {
            if (project.RestoreMetadata != null)
            {
                foreach (var framework in project.RestoreMetadata.TargetFrameworks.NoAllocEnumerate())
                {
                    if (string.Equals(framework.TargetAlias, targetAlias, StringComparison.OrdinalIgnoreCase))
                    {
                        return framework;
                    }
                }
            }
            return null;
        }


        /// <summary>
        /// Gets the nearest target framework within the project based on the given target framework and target alias.
        /// If multiple frameworks match, the target alias is used to disambiguate.
        /// If there are duplicates and no matching alias, then an empty TargetFrameworkInformation is returned.
        /// </summary>
        /// <param name="project">The project spec in question.</param>
        /// <param name="targetFramework">The pivot target framework.</param>
        /// <param name="targetAlias">The alias for disambiguation</param>
        public static TargetFrameworkInformation GetNearestTargetFramework(this PackageSpec project, NuGetFramework targetFramework, string? targetAlias)
        {
            TargetFrameworkInformation? result = null;
            List<TargetFrameworkInformation>? frameworks = null;
            FindMatchingFrameworks(project, targetFramework, ref result, ref frameworks);

            // We matched a single framework.
            if (result != null && frameworks == null)
            {
                return result;
            }

            // No exact match found, find the nearest
            if (result == null || frameworks == null)
            {
                var reducer = new FrameworkReducer(DefaultFrameworkNameProvider.Instance, DefaultCompatibilityProvider.Instance);
                NuGetFramework? mostCompatibleFramework = reducer.GetNearest(targetFramework, project.TargetFrameworks.Select(e => e.FrameworkName));
                if (mostCompatibleFramework != null)
                {
                    FindMatchingFrameworks(project, mostCompatibleFramework, ref result, ref frameworks);
                }
            }

            // We matched a single framework.
            if (result != null && frameworks == null)
            {
                return result;
            }

            // Multiple matchingFrameworks matched exactly, use target alias to disambiguate
            if (result != null && frameworks != null)
            {
                foreach (var framework in frameworks)
                {
                    if (string.Equals(framework.TargetAlias, targetAlias, StringComparison.OrdinalIgnoreCase))
                    {
                        return framework;
                    }
                }
            }

            return new TargetFrameworkInformation();

            static void FindMatchingFrameworks(PackageSpec project, NuGetFramework targetFramework, ref TargetFrameworkInformation? matchedFramework, ref List<TargetFrameworkInformation>? matchingFrameworks)
            {
                foreach (TargetFrameworkInformation framework in project.TargetFrameworks.NoAllocEnumerate())
                {
                    if (NuGetFramework.Comparer.Equals(targetFramework, framework.FrameworkName))
                    {
                        // Result not being null means we have multiple matches.
                        if (matchedFramework != null)
                        {
                            if (matchingFrameworks == null)
                            {
                                matchingFrameworks = [matchedFramework, framework];
                            }
                            else
                            {
                                matchingFrameworks.Add(framework);
                            }
                        }
                        matchedFramework = framework;
                    }
                }
            }
        }
    }
}