File: FrameworkPackages\FrameworkPackages.cs
Web Access
Project: ..\..\..\src\Tasks\Microsoft.NET.Build.Tasks\Microsoft.NET.Build.Tasks.csproj (Microsoft.NET.Build.Tasks)
namespace Microsoft.ComponentDetection.Detectors.NuGet;
 
#nullable disable
 
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using global::NuGet.Frameworks;
using global::NuGet.Versioning;
using Microsoft.NET.Build.Tasks;
using Microsoft.NET.Build.Tasks.ConflictResolution;
 
/// <summary>
/// Represents a set of packages that are provided by a specific framework.
/// At the moment this only represents the packages that are provided by the Microsoft.NETCore.App framework.
/// We could extend this to represent the packages provided by other frameworks like Microsoft.AspNetCore.App and Microsoft.WindowsDesktop.App.
/// </summary>
internal sealed partial class FrameworkPackages : IEnumerable<KeyValuePair<string, NuGetVersion>>, IEnumerable
{
    private const string DefaultFrameworkKey = "";
    private static readonly ConcurrentDictionary<NuGetFramework, ConcurrentDictionary<string, FrameworkPackages>> FrameworkPackagesByFramework = [];
 
    static FrameworkPackages()
    {
        NETStandard20.Register();
        NETStandard21.Register();
        NETCoreApp20.Register();
        NETCoreApp21.Register();
        NETCoreApp22.Register();
        NETCoreApp30.Register();
        NETCoreApp31.Register();
        NETCoreApp50.Register();
        NETCoreApp60.Register();
        NETCoreApp70.Register();
        NETCoreApp80.Register();
        NETCoreApp90.Register();
    }
 
    public FrameworkPackages(NuGetFramework framework, string frameworkName)
    {
        this.Framework = framework;
        this.FrameworkName = frameworkName;
    }
 
    public FrameworkPackages(NuGetFramework framework, string frameworkName, FrameworkPackages frameworkPackages)
        : this(framework, frameworkName) => this.Packages = new(frameworkPackages.Packages);
 
    public NuGetFramework Framework { get; }
 
    public string FrameworkName { get; }
 
    public Dictionary<string, NuGetVersion> Packages { get; } = new Dictionary<string, NuGetVersion>(StringComparer.OrdinalIgnoreCase);
 
    private static string GetFrameworkKey(string frameworkName) =>
        frameworkName switch
        {
            FrameworkNames.NetStandardLibrary => DefaultFrameworkKey,
            FrameworkNames.NetCoreApp => DefaultFrameworkKey,
            _ => frameworkName,
        };
 
    internal static void Register(params FrameworkPackages[] toRegister)
    {
        foreach (var frameworkPackages in toRegister)
        {
            if (!FrameworkPackagesByFramework.TryGetValue(frameworkPackages.Framework, out var frameworkPackagesForVersion))
            {
                FrameworkPackagesByFramework[frameworkPackages.Framework] = frameworkPackagesForVersion = [];
            }
 
            var frameworkKey = GetFrameworkKey(frameworkPackages.FrameworkName);
            frameworkPackagesForVersion[frameworkKey] = frameworkPackages;
        }
    }
 
    public static FrameworkPackages[] GetFrameworkPackages(NuGetFramework framework, string[] frameworkReferences, bool acceptNearestMatch = false)
    {
        var frameworkPackages = new List<FrameworkPackages>();
 
        if (frameworkReferences.Length == 0)
        {
            frameworkReferences = [DefaultFrameworkKey];
        }
 
        foreach (var frameworkReference in frameworkReferences)
        {
            var frameworkKey = GetFrameworkKey(frameworkReference);
 
            NuGetFramework nearestFramework;
 
            if (acceptNearestMatch)
            {
                var reducer = new FrameworkReducer();
                var candidateFrameworks = FrameworkPackagesByFramework.Where(pair => pair.Value.ContainsKey(frameworkKey)).Select(pair => pair.Key);
                nearestFramework = reducer.GetNearest(framework, candidateFrameworks);
            }
            else
            {
                nearestFramework = framework;
            }
 
            if (FrameworkPackagesByFramework.TryGetValue(nearestFramework, out var frameworkPackagesForVersion) &&
                frameworkPackagesForVersion.TryGetValue(frameworkKey, out var frameworkPackage))
            {
                frameworkPackages.Add(frameworkPackage);
            }
        }
 
        return frameworkPackages.ToArray();
    }
 
    public static FrameworkPackages LoadFrameworkPackagesFromPack(Logger log, NuGetFramework framework, string frameworkName, string targetingPackRoot)
    {
        if (framework is null || framework.Framework != FrameworkConstants.FrameworkIdentifiers.NetCoreApp)
        {
            return null;
        }
 
        if (!string.IsNullOrEmpty(targetingPackRoot))
        {
            var packsFolder = Path.Combine(targetingPackRoot, frameworkName + ".Ref");
            log.LogMessage("Looking for targeting packs in {0}", packsFolder);
            if (Directory.Exists(packsFolder))
            {
                var packVersionPattern = $"{framework.Version.Major}.{framework.Version.Minor}.*";
                var packDirectories = Directory.GetDirectories(packsFolder, packVersionPattern);
                log.LogMessage("Pack directories found: {0}", string.Join(Environment.NewLine, packDirectories));
                var packageOverridesFile = packDirectories
                                                .Select(d => (Overrides: Path.Combine(d, "data", "PackageOverrides.txt"), Version: ParseVersion(Path.GetFileName(d))))
                                                .Where(d => File.Exists(d.Overrides))
                                                .OrderByDescending(d => d.Version)
                                                .FirstOrDefault().Overrides;
 
                if (packageOverridesFile is not null)
                {
                    log.LogMessage("Found package overrides file {0}", packageOverridesFile);
                    // Adapted from https://github.com/dotnet/sdk/blob/c3a8f72c3a5491c693ff8e49e7406136a12c3040/src/Tasks/Common/ConflictResolution/PackageOverride.cs#L52-L68
                    var packageOverrideLines = File.ReadAllLines(packageOverridesFile);
 
                    var frameworkPackages = new FrameworkPackages(framework, frameworkName);
                    foreach (var packageOverride in PackageOverride.CreateOverriddenPackages(packageOverrideLines))
                    {
                        frameworkPackages.Packages[packageOverride.Item1] = packageOverride.Item2;
                    }
 
                    return frameworkPackages;
                }
                else
                {
                    log.LogMessage("No package overrides found in {0}", packsFolder);
                }
            }
            else
            {
                log.LogMessage("Targeting pack folder {0} does not exist", packsFolder);
            }
        }
 
        return null;
 
        static NuGetVersion ParseVersion(string versionString) => NuGetVersion.TryParse(versionString, out var version) ? version : null;
    }
 
    private void Add(string id, string version)
    {
        if (string.IsNullOrEmpty(version))
        {
            this.Packages.Remove(id);
        }
        else
        {
            // intentionally redirect to indexer to allow for overwrite
            this.Packages[id] = NuGetVersion.Parse(version);
        }
    }
 
    public bool IsAFrameworkComponent(string id, NuGetVersion version) => this.Packages.TryGetValue(id, out var frameworkPackageVersion) && frameworkPackageVersion >= version;
 
    IEnumerator<KeyValuePair<string, NuGetVersion>> IEnumerable<KeyValuePair<string, NuGetVersion>>.GetEnumerator() => this.Packages.GetEnumerator();
 
    IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();
 
    internal static class FrameworkNames
    {
        public const string AspNetCoreApp = "Microsoft.AspNetCore.App";
        public const string NetCoreApp = "Microsoft.NETCore.App";
        public const string NetStandardLibrary = "NETStandard.Library";
        public const string WindowsDesktopApp = "Microsoft.WindowsDesktop.App";
    }
}