File: ProcessFrameworkReferences.cs
Web Access
Project: ..\..\..\src\Tasks\Microsoft.NET.Build.Tasks\Microsoft.NET.Build.Tasks.csproj (Microsoft.NET.Build.Tasks)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Configurer;
using Microsoft.NET.Sdk.WorkloadManifestReader;
using Newtonsoft.Json;
using NuGet.Frameworks;
using NuGet.Versioning;
 
namespace Microsoft.NET.Build.Tasks
{
    /// <summary>
    /// This class processes the FrameworkReference items.  It adds PackageReferences for the
    /// targeting packs which provide the reference assemblies, and creates RuntimeFramework
    /// items, which are written to the runtimeconfig file
    /// </summary>
    public class ProcessFrameworkReferences : TaskBase
    {
        public string? TargetFrameworkIdentifier { get; set; }
 
        [Required]
        public string TargetFrameworkVersion { get; set; } = null!;
 
        public string? TargetPlatformIdentifier { get; set; }
 
        public string? TargetPlatformVersion { get; set; }
 
        public string? TargetingPackRoot { get; set; }
 
        [Required]
        public string RuntimeGraphPath { get; set; } = null!;
 
        public bool SelfContained { get; set; }
 
        public bool ReadyToRunEnabled { get; set; }
 
        public bool ReadyToRunUseCrossgen2 { get; set; }
 
        public bool PublishAot { get; set; }
 
        public bool RequiresILLinkPack { get; set; }
 
        public bool IsAotCompatible { get; set; }
 
        public bool SilenceIsAotCompatibleUnsupportedWarning { get; set; }
 
        public string? MinNonEolTargetFrameworkForAot { get; set; }
 
        public bool EnableAotAnalyzer { get; set; }
 
        public string? FirstTargetFrameworkVersionToSupportAotAnalyzer { get; set; }
 
        public bool PublishTrimmed { get; set; }
 
        public bool IsTrimmable { get; set; }
 
        public string? FirstTargetFrameworkVersionToSupportTrimAnalyzer { get; set; }
 
        public bool SilenceIsTrimmableUnsupportedWarning { get; set; }
 
        public string? MinNonEolTargetFrameworkForTrimming { get; set; }
 
        public bool EnableTrimAnalyzer { get; set; }
 
        public bool EnableSingleFileAnalyzer { get; set; }
 
        public string? FirstTargetFrameworkVersionToSupportSingleFileAnalyzer { get; set; }
 
        public bool SilenceEnableSingleFileAnalyzerUnsupportedWarning { get; set; }
 
        public string? MinNonEolTargetFrameworkForSingleFile { get; set; }
 
        public bool AotUseKnownRuntimePackForTarget { get; set; }
 
        public string? RuntimeIdentifier { get; set; }
 
        /// <summary>
        /// Since this Target is mostly focused on managing RID-specific assets, we massage the 'any' RID (which is platform-agnostic) into a 'null'
        /// value to make processing simpler.
        /// </summary>
        public string? EffectiveRuntimeIdentifier => RuntimeIdentifier == "any" ? null : RuntimeIdentifier;
 
        public string[]? RuntimeIdentifiers { get; set; }
 
        public string? RuntimeFrameworkVersion { get; set; }
 
        public bool TargetLatestRuntimePatch { get; set; }
 
        public bool TargetLatestRuntimePatchIsDefault { get; set; }
 
        public bool EnableTargetingPackDownload { get; set; }
 
        public bool EnableRuntimePackDownload { get; set; }
 
        public bool EnableWindowsTargeting { get; set; }
 
        public bool DisableTransitiveFrameworkReferenceDownloads { get; set; }
 
        public ITaskItem[] FrameworkReferences { get; set; } = Array.Empty<ITaskItem>();
 
        public ITaskItem[] KnownFrameworkReferences { get; set; } = Array.Empty<ITaskItem>();
 
        public ITaskItem[] KnownRuntimePacks { get; set; } = Array.Empty<ITaskItem>();
 
        public ITaskItem[] KnownCrossgen2Packs { get; set; } = Array.Empty<ITaskItem>();
 
        public ITaskItem[] KnownILCompilerPacks { get; set; } = Array.Empty<ITaskItem>();
 
        public ITaskItem[] KnownILLinkPacks { get; set; } = Array.Empty<ITaskItem>();
 
        public ITaskItem[] KnownWebAssemblySdkPacks { get; set; } = Array.Empty<ITaskItem>();
 
        public ITaskItem[] KnownAspNetCorePacks { get; set; } = Array.Empty<ITaskItem>();
 
        public bool UsingMicrosoftNETSdkWebAssembly { get; set; }
 
        public bool RequiresAspNetWebAssets { get; set; }
 
        [Required]
        public string NETCoreSdkRuntimeIdentifier { get; set; } = null!;
 
        public string? NETCoreSdkPortableRuntimeIdentifier { get; set; }
 
        [Required]
        public string NetCoreRoot { get; set; } = null!;
 
        [Required]
        public string NETCoreSdkVersion { get; set; } = null!;
 
        [Output]
        public ITaskItem[]? PackagesToDownload { get; set; }
 
        [Output]
        public ITaskItem[]? RuntimeFrameworks { get; set; }
 
        [Output]
        public ITaskItem[]? TargetingPacks { get; set; }
 
        [Output]
        public ITaskItem[]? RuntimePacks { get; set; }
 
        [Output]
        public ITaskItem[]? Crossgen2Packs { get; set; }
 
        [Output]
        public ITaskItem[]? HostILCompilerPacks { get; set; }
 
        [Output]
        public ITaskItem[]? TargetILCompilerPacks { get; set; }
 
        [Output]
        public ITaskItem[]? ImplicitPackageReferences { get; set; }
 
        //  Runtime packs which aren't available for the specified RuntimeIdentifier
        [Output]
        public ITaskItem[]? UnavailableRuntimePacks { get; set; }
 
        [Output]
        public string[]? KnownRuntimeIdentifierPlatforms { get; set; }
 
        private Version? _normalizedTargetFrameworkVersion;
 
        void AddPacksForFrameworkReferences(
            List<ITaskItem> packagesToDownload,
            List<ITaskItem> runtimeFrameworks,
            List<ITaskItem> targetingPacks,
            List<ITaskItem> runtimePacks,
            List<ITaskItem> unavailableRuntimePacks,
            out List<KnownRuntimePack> knownRuntimePacksForTargetFramework
        )
        {
            var knownFrameworkReferencesForTargetFramework =
                KnownFrameworkReferences
                    .Select(item => new KnownFrameworkReference(item))
                    .Where(kfr => KnownFrameworkReferenceAppliesToTargetFramework(kfr.TargetFramework))
                    .ToList();
            //  Get known runtime packs from known framework references.
            //  Only use items where the framework reference name matches the RuntimeFrameworkName.
            //  This will filter out known framework references for "profiles", ie WindowsForms and WPF
            knownRuntimePacksForTargetFramework =
                knownFrameworkReferencesForTargetFramework
                    .Where(kfr => kfr.Name.Equals(kfr.RuntimeFrameworkName, StringComparison.OrdinalIgnoreCase))
                    .Select(kfr => kfr.ToKnownRuntimePack())
                    .ToList();
 
            //  Add additional known runtime packs
            knownRuntimePacksForTargetFramework.AddRange(
                KnownRuntimePacks.Select(item => new KnownRuntimePack(item))
                                 .Where(krp => KnownFrameworkReferenceAppliesToTargetFramework(krp.TargetFramework)));
 
            var frameworkReferenceMap = FrameworkReferences.ToDictionary(fr => fr.ItemSpec, StringComparer.OrdinalIgnoreCase);
            Log.LogMessage(MessageImportance.Low, $"Found {frameworkReferenceMap.Count} known framework references for target framework {TargetFrameworkIdentifier}");
            Log.LogMessage(MessageImportance.Low, $"Found {knownRuntimePacksForTargetFramework.Count} known runtime packs for target framework {TargetFrameworkIdentifier}");
 
            HashSet<string> unrecognizedRuntimeIdentifiers = new(StringComparer.OrdinalIgnoreCase);
 
            bool windowsOnlyErrorLogged = false;
            foreach (var knownFrameworkReference in knownFrameworkReferencesForTargetFramework)
            {
                frameworkReferenceMap.TryGetValue(knownFrameworkReference.Name, out ITaskItem? frameworkReference);
 
                // Handle Windows-only frameworks on non-Windows platforms
                if (knownFrameworkReference.IsWindowsOnly &&
                    !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
                    !EnableWindowsTargeting)
                {
                    // It is an error to reference the framework from non-Windows
                    if (!windowsOnlyErrorLogged && frameworkReference != null)
                    {
                        Log.LogError(Strings.WindowsDesktopFrameworkRequiresWindows);
                        windowsOnlyErrorLogged = true;
                    }
                    Log.LogMessage(MessageImportance.Low, $"Ignoring framework reference to {knownFrameworkReference.Name} as it is Windows-only and the current platform is not Windows.");
                    // Ignore (and don't download) this known framework reference as it requires Windows
                    continue;
                }
 
                KnownRuntimePack? selectedRuntimePack = SelectRuntimePack(frameworkReference, knownFrameworkReference, knownRuntimePacksForTargetFramework);
 
                //  Add targeting pack and all known runtime packs to "preferred packages" list.
                //  These are packages that will win in conflict resolution for assets that have identical assembly and file versions
                var preferredPackages = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
                {
                    knownFrameworkReference.TargetingPackName
                };
 
                if (selectedRuntimePack is KnownRuntimePack selectedPack)
                {
                    var knownFrameworkReferenceRuntimePackRuntimeIdentifiers = selectedPack.RuntimePackRuntimeIdentifiers.Split(';');
                    foreach (var runtimeIdentifier in knownFrameworkReferenceRuntimePackRuntimeIdentifiers)
                    {
                        foreach (var runtimePackNamePattern in selectedPack.RuntimePackNamePatterns.Split(';'))
                        {
                            string runtimePackName = runtimePackNamePattern.Replace("**RID**", runtimeIdentifier);
                            preferredPackages.Add(runtimePackName);
                        }
                    }
                    Log.LogMessage(MessageImportance.Low, $"Selected {selectedPack.Name} with RIDs '{selectedPack.RuntimePackRuntimeIdentifiers}'");
                }
                else
                {
                    Log.LogMessage(MessageImportance.Low, $"No runtime pack found for {knownFrameworkReference.Name}.");
                }
 
                TaskItem targetingPack = new(knownFrameworkReference.Name);
                targetingPack.SetMetadata(MetadataKeys.NuGetPackageId, knownFrameworkReference.TargetingPackName);
                targetingPack.SetMetadata(MetadataKeys.PackageConflictPreferredPackages, string.Join(";", preferredPackages));
 
                string? targetingPackVersion = null;
                if (frameworkReference != null)
                {
                    //  Allow targeting pack version to be overridden via metadata on FrameworkReference
                    targetingPackVersion = frameworkReference.GetMetadata("TargetingPackVersion");
                }
                if (string.IsNullOrEmpty(targetingPackVersion))
                {
                    targetingPackVersion = knownFrameworkReference.TargetingPackVersion;
                }
 
                //  Look up targeting pack version from workload manifests if necessary
                targetingPackVersion = GetResolvedPackVersion(knownFrameworkReference.TargetingPackName, targetingPackVersion);
 
                targetingPack.SetMetadata(MetadataKeys.NuGetPackageVersion, targetingPackVersion);
                targetingPack.SetMetadata("TargetingPackFormat", knownFrameworkReference.TargetingPackFormat);
                targetingPack.SetMetadata("TargetFramework", knownFrameworkReference.TargetFramework.GetShortFolderName());
                targetingPack.SetMetadata(MetadataKeys.RuntimeFrameworkName, knownFrameworkReference.RuntimeFrameworkName);
                if (selectedRuntimePack != null)
                {
                    targetingPack.SetMetadata(MetadataKeys.RuntimePackRuntimeIdentifiers, selectedRuntimePack?.RuntimePackRuntimeIdentifiers);
                }
 
                if (!string.IsNullOrEmpty(knownFrameworkReference.Profile))
                {
                    targetingPack.SetMetadata("Profile", knownFrameworkReference.Profile);
                }
 
                //  Get the path of the targeting pack in the targeting pack root (e.g. dotnet/packs)
                string? targetingPackPath = GetPackPath(knownFrameworkReference.TargetingPackName, targetingPackVersion);
                if (targetingPackPath != null)
                {
                    // Use targeting pack from packs folder
                    targetingPack.SetMetadata(MetadataKeys.PackageDirectory, targetingPackPath);
                    targetingPack.SetMetadata(MetadataKeys.Path, targetingPackPath);
                }
                else
                {
                    //  If transitive framework reference downloads are disabled, then don't download targeting packs where there isn't
                    //  a direct framework reference
                    if (EnableTargetingPackDownload &&
                        !(DisableTransitiveFrameworkReferenceDownloads && frameworkReference == null))
                    {
                        //  Download targeting pack
                        TaskItem packageToDownload = new(knownFrameworkReference.TargetingPackName);
                        packageToDownload.SetMetadata(MetadataKeys.Version, targetingPackVersion);
 
                        packagesToDownload.Add(packageToDownload);
                    }
                }
 
                targetingPacks.Add(targetingPack);
 
                Log.LogMessage(MessageImportance.Low, $"Selected targeting pack '{targetingPack.ItemSpec}@{targetingPackVersion}'");
 
                string runtimeFrameworkVersion = GetRuntimeFrameworkVersion(
                    frameworkReference,
                    knownFrameworkReference,
                    selectedRuntimePack,
                    out string runtimePackVersion);
 
                string? isTrimmable = null;
                if (frameworkReference != null)
                {
                    // Allow IsTrimmable to be overridden via metadata on FrameworkReference
                    isTrimmable = frameworkReference.GetMetadata(MetadataKeys.IsTrimmable);
                }
                if (string.IsNullOrEmpty(isTrimmable))
                {
                    isTrimmable = selectedRuntimePack?.IsTrimmable;
                }
 
                bool useRuntimePackAndDownloadIfNecessary;
                KnownRuntimePack runtimePackForRuntimeIDProcessing;
                if (knownFrameworkReference.Name.Equals(knownFrameworkReference.RuntimeFrameworkName, StringComparison.OrdinalIgnoreCase) && selectedRuntimePack != null)
                {
                    //  Only add runtime packs where the framework reference name matches the RuntimeFrameworkName
                    //  Framework references for "profiles" will use the runtime pack from the corresponding non-profile framework
                    runtimePackForRuntimeIDProcessing = selectedRuntimePack.Value;
                    useRuntimePackAndDownloadIfNecessary = true;
                }
                else if (!knownFrameworkReference.RuntimePackRuntimeIdentifiers.Equals(selectedRuntimePack?.RuntimePackRuntimeIdentifiers))
                {
                    // If the profile has a different set of runtime identifiers than the runtime pack, use the profile.
                    runtimePackForRuntimeIDProcessing = knownFrameworkReference.ToKnownRuntimePack();
                    useRuntimePackAndDownloadIfNecessary = true;
                }
                else
                {
                    // For the remaining profiles, don't include them in package download but add them to unavailable if necessary.
                    runtimePackForRuntimeIDProcessing = knownFrameworkReference.ToKnownRuntimePack();
                    useRuntimePackAndDownloadIfNecessary = false;
                }
 
                bool processedPrimaryRuntimeIdentifier = false;
 
                var hasRuntimePackAlwaysCopyLocal =
                    selectedRuntimePack != null && selectedRuntimePack.Value.RuntimePackAlwaysCopyLocal;
                var runtimeRequiredByDeployment
                    = (SelfContained || ReadyToRunEnabled) &&
                      !string.IsNullOrEmpty(EffectiveRuntimeIdentifier) &&
                      !string.IsNullOrEmpty(selectedRuntimePack?.RuntimePackNamePatterns);
 
                if (hasRuntimePackAlwaysCopyLocal || runtimeRequiredByDeployment)
                {
                    //  Find other KnownFrameworkReferences that map to the same runtime pack, if any
                    List<string>? additionalFrameworkReferencesForRuntimePack = null;
                    foreach (var additionalKnownFrameworkReference in knownFrameworkReferencesForTargetFramework)
                    {
                        if (additionalKnownFrameworkReference.RuntimeFrameworkName.Equals(knownFrameworkReference.RuntimeFrameworkName, StringComparison.OrdinalIgnoreCase) &&
                            !additionalKnownFrameworkReference.RuntimeFrameworkName.Equals(additionalKnownFrameworkReference.Name, StringComparison.OrdinalIgnoreCase))
                        {
                            additionalFrameworkReferencesForRuntimePack ??= [];
                            additionalFrameworkReferencesForRuntimePack.Add(additionalKnownFrameworkReference.Name);
                        }
                    }
 
                    //  Process primary runtime identifier
                    ProcessRuntimeIdentifier(EffectiveRuntimeIdentifier ?? "any", runtimePackForRuntimeIDProcessing, runtimePackVersion, additionalFrameworkReferencesForRuntimePack,
                        unrecognizedRuntimeIdentifiers, unavailableRuntimePacks, runtimePacks, packagesToDownload, isTrimmable, useRuntimePackAndDownloadIfNecessary,
                        wasReferencedDirectly: frameworkReference != null);
 
                    processedPrimaryRuntimeIdentifier = true;
                }
 
                if (RuntimeIdentifiers != null)
                {
                    foreach (var runtimeIdentifier in RuntimeIdentifiers)
                    {
                        if (processedPrimaryRuntimeIdentifier && runtimeIdentifier == EffectiveRuntimeIdentifier)
                        {
                            //  We've already processed this RID
                            continue;
                        }
 
                        if (runtimeIdentifier == "any")
                        {
                            // The `any` RID represents a platform-agnostic target. As such, it has no
                            // platform-specific runtime pack associated with it.
                            continue;
                        }
 
                        //  Pass in null for the runtimePacks list, as for these runtime identifiers we only want to
                        //  download the runtime packs, but not use the assets from them
                        ProcessRuntimeIdentifier(runtimeIdentifier, runtimePackForRuntimeIDProcessing, runtimePackVersion, additionalFrameworkReferencesForRuntimePack: null,
                            unrecognizedRuntimeIdentifiers, unavailableRuntimePacks, runtimePacks: null, packagesToDownload, isTrimmable, useRuntimePackAndDownloadIfNecessary,
                            wasReferencedDirectly: frameworkReference != null);
                    }
                }
 
                if (!string.IsNullOrEmpty(knownFrameworkReference.RuntimeFrameworkName) && !knownFrameworkReference.RuntimePackAlwaysCopyLocal)
                {
                    TaskItem runtimeFramework = new(knownFrameworkReference.RuntimeFrameworkName);
 
                    runtimeFramework.SetMetadata(MetadataKeys.Version, runtimeFrameworkVersion);
                    runtimeFramework.SetMetadata(MetadataKeys.FrameworkName, knownFrameworkReference.Name);
                    runtimeFramework.SetMetadata("Profile", knownFrameworkReference.Profile);
 
                    runtimeFrameworks.Add(runtimeFramework);
                    Log.LogMessage(MessageImportance.Low, $"Added runtime framework '{runtimeFramework.ItemSpec}@{runtimeFrameworkVersion}'");
                }
            }
        }
 
        protected override void ExecuteCore()
        {
            List<ITaskItem>? packagesToDownload = null;
            List<ITaskItem>? runtimeFrameworks = null;
            List<ITaskItem>? targetingPacks = null;
            List<ITaskItem>? runtimePacks = null;
            List<ITaskItem>? unavailableRuntimePacks = null;
            List<KnownRuntimePack>? knownRuntimePacksForTargetFramework = null;
 
            //  Perf optimization: If there are no FrameworkReference items, then don't do anything
            //  (This means that if you don't have any direct framework references, you won't get any transitive ones either
            if (FrameworkReferences != null && FrameworkReferences.Length != 0)
            {
                _normalizedTargetFrameworkVersion = NormalizeVersion(new Version(TargetFrameworkVersion));
 
                packagesToDownload = new List<ITaskItem>();
                runtimeFrameworks = new List<ITaskItem>();
                targetingPacks = new List<ITaskItem>();
                runtimePacks = new List<ITaskItem>();
                unavailableRuntimePacks = new List<ITaskItem>();
                AddPacksForFrameworkReferences(
                    packagesToDownload,
                    runtimeFrameworks,
                    targetingPacks,
                    runtimePacks,
                    unavailableRuntimePacks,
                    out knownRuntimePacksForTargetFramework);
            }
 
            _normalizedTargetFrameworkVersion ??= NormalizeVersion(new Version(TargetFrameworkVersion));
            packagesToDownload ??= new List<ITaskItem>();
 
            List<ITaskItem> implicitPackageReferences = new();
 
            if (ReadyToRunEnabled && ReadyToRunUseCrossgen2)
            {
                if (AddToolPack(ToolPackType.Crossgen2, _normalizedTargetFrameworkVersion, packagesToDownload, implicitPackageReferences) is not ToolPackSupport.Supported)
                {
                    Log.LogError(Strings.ReadyToRunNoValidRuntimePackageError);
                    return;
                }
            }
 
            if (PublishAot)
            {
                switch (AddToolPack(ToolPackType.ILCompiler, _normalizedTargetFrameworkVersion, packagesToDownload, implicitPackageReferences))
                {
                    case ToolPackSupport.UnsupportedForTargetFramework:
                        Log.LogError(Strings.AotUnsupportedTargetFramework);
                        return;
                    case ToolPackSupport.UnsupportedForHostRuntimeIdentifier:
                        Log.LogError(Strings.AotUnsupportedHostRuntimeIdentifier, NETCoreSdkRuntimeIdentifier);
                        return;
                    case ToolPackSupport.UnsupportedForTargetRuntimeIdentifier when EffectiveRuntimeIdentifier != null:
                        Log.LogError(Strings.AotUnsupportedTargetRuntimeIdentifier, EffectiveRuntimeIdentifier!);
                        return;
                    case ToolPackSupport.Supported:
                        break;
                }
            }
 
            if (RequiresILLinkPack)
            {
                if (AddToolPack(ToolPackType.ILLink, _normalizedTargetFrameworkVersion, packagesToDownload, implicitPackageReferences) is not ToolPackSupport.Supported)
                {
                    // Keep the checked properties in sync with _RequiresILLinkPack in Microsoft.NET.Publish.targets.
                    if (PublishAot)
                    {
                        // If PublishAot is set, this should produce a specific error above already.
                        // Also produce one here just in case there are custom KnownILCompilerPack/KnownILLinkPack
                        // items that bypass the error above.
                        Log.LogError(Strings.AotUnsupportedTargetFramework);
                    }
                    else if (IsAotCompatible || EnableAotAnalyzer)
                    {
                        if (!SilenceIsAotCompatibleUnsupportedWarning)
                            Log.LogWarning(Strings.IsAotCompatibleUnsupported, MinNonEolTargetFrameworkForAot!);
                    }
                    else if (PublishTrimmed)
                    {
                        Log.LogError(Strings.PublishTrimmedRequiresVersion30);
                    }
                    else if (IsTrimmable || EnableTrimAnalyzer)
                    {
                        if (!SilenceIsTrimmableUnsupportedWarning)
                            Log.LogWarning(Strings.IsTrimmableUnsupported, MinNonEolTargetFrameworkForTrimming!);
                    }
                    else if (EnableSingleFileAnalyzer)
                    {
                        // There's no IsSingleFileCompatible setting. EnableSingleFileAnalyzer is the
                        // recommended way to ensure single-file compatibility for libraries.
                        if (!SilenceEnableSingleFileAnalyzerUnsupportedWarning)
                            Log.LogWarning(Strings.EnableSingleFileAnalyzerUnsupported, MinNonEolTargetFrameworkForSingleFile!);
                    }
                    else
                    {
                        // _RequiresILLinkPack was set. This setting acts as an override for the
                        // user-visible properties, and should generally only be used by
                        // other SDKs that can't use the other properties for some reason.
                        Log.LogError(Strings.ILLinkNoValidRuntimePackageError);
                    }
                }
            }
 
            if (UsingMicrosoftNETSdkWebAssembly)
            {
                // WebAssemblySdk is used for .NET >= 6, it's ok if no pack is added.
                AddToolPack(ToolPackType.WebAssemblySdk, _normalizedTargetFrameworkVersion, packagesToDownload, implicitPackageReferences);
            }
 
            if (RequiresAspNetWebAssets && _normalizedTargetFrameworkVersion.Major >= 10)
            {
                if (AddToolPack(ToolPackType.AspNetCore, _normalizedTargetFrameworkVersion, packagesToDownload, implicitPackageReferences) is not ToolPackSupport.Supported)
                {
                    Log.LogWarning(Strings.AspNetCorePackUnsupportedTargetFramework);
                }
            }
 
            if (packagesToDownload.Any())
            {
                PackagesToDownload = packagesToDownload.Distinct(new PackageToDownloadComparer<ITaskItem>()).ToArray();
            }
 
            if (runtimeFrameworks?.Any() == true)
            {
                RuntimeFrameworks = runtimeFrameworks.ToArray();
            }
 
            if (targetingPacks?.Any() == true)
            {
                TargetingPacks = targetingPacks.ToArray();
            }
 
            if (runtimePacks?.Any() == true)
            {
                RuntimePacks = runtimePacks.ToArray();
            }
 
            if (unavailableRuntimePacks?.Any() == true)
            {
                UnavailableRuntimePacks = unavailableRuntimePacks.ToArray();
            }
 
            if (implicitPackageReferences.Any())
            {
                ImplicitPackageReferences = implicitPackageReferences.ToArray();
            }
 
            if (knownRuntimePacksForTargetFramework?.Any() == true)
            {
                // Determine the known runtime identifier platforms based on all available Microsoft.NETCore.App packs
                HashSet<string> knownRuntimeIdentifierPlatforms = new(StringComparer.OrdinalIgnoreCase);
                var netCoreAppPacks = knownRuntimePacksForTargetFramework!.Where(krp => krp.Name.Equals("Microsoft.NETCore.App", StringComparison.OrdinalIgnoreCase));
                foreach (KnownRuntimePack netCoreAppPack in netCoreAppPacks)
                {
                    foreach (var runtimeIdentifier in netCoreAppPack.RuntimePackRuntimeIdentifiers.Split(';'))
                    {
                        int separator = runtimeIdentifier.LastIndexOf('-');
                        string platform = separator < 0 ? runtimeIdentifier : runtimeIdentifier.Substring(0, separator);
                        knownRuntimeIdentifierPlatforms.Add(platform);
                    }
                }
 
                if (knownRuntimeIdentifierPlatforms.Count > 0)
                {
                    KnownRuntimeIdentifierPlatforms = knownRuntimeIdentifierPlatforms.ToArray();
                }
            }
        }
 
        private bool KnownFrameworkReferenceAppliesToTargetFramework(NuGetFramework knownFrameworkReferenceTargetFramework)
        {
            if (!knownFrameworkReferenceTargetFramework.Framework.Equals(TargetFrameworkIdentifier, StringComparison.OrdinalIgnoreCase)
                || NormalizeVersion(knownFrameworkReferenceTargetFramework.Version) != _normalizedTargetFrameworkVersion)
            {
                return false;
            }
 
            if (!string.IsNullOrEmpty(knownFrameworkReferenceTargetFramework.Platform)
                && knownFrameworkReferenceTargetFramework.PlatformVersion != null)
            {
                if (!knownFrameworkReferenceTargetFramework.Platform.Equals(TargetPlatformIdentifier, StringComparison.OrdinalIgnoreCase))
                {
                    return false;
                }
 
                if (!Version.TryParse(TargetPlatformVersion, out var targetPlatformVersionParsed))
                {
                    return false;
                }
 
                if (NormalizeVersion(targetPlatformVersionParsed) != NormalizeVersion(knownFrameworkReferenceTargetFramework.PlatformVersion)
                    || NormalizeVersion(knownFrameworkReferenceTargetFramework.Version) != _normalizedTargetFrameworkVersion)
                {
                    return false;
                }
            }
 
            return true;
        }
 
        private KnownRuntimePack? SelectRuntimePack(ITaskItem? frameworkReference, KnownFrameworkReference knownFrameworkReference, List<KnownRuntimePack> knownRuntimePacks)
        {
            var requiredLabelsMetadata = frameworkReference?.GetMetadata(MetadataKeys.RuntimePackLabels) ?? "";
 
            HashSet<string>? requiredRuntimePackLabels = null;
            if (frameworkReference != null)
            {
                requiredRuntimePackLabels = new HashSet<string>(requiredLabelsMetadata.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries), StringComparer.OrdinalIgnoreCase);
            }
 
            //  The runtime pack name matches the RuntimeFrameworkName on the KnownFrameworkReference
            var matchingRuntimePacks = knownRuntimePacks.Where(krp => krp.Name.Equals(knownFrameworkReference.RuntimeFrameworkName, StringComparison.OrdinalIgnoreCase))
                .Where(krp =>
                {
                    if (requiredRuntimePackLabels == null)
                    {
                        return krp.RuntimePackLabels.Length == 0;
                    }
                    else
                    {
                        return requiredRuntimePackLabels.SetEquals(krp.RuntimePackLabels);
                    }
                })
                .ToList();
 
            if (matchingRuntimePacks.Count == 0)
            {
                return null;
            }
            else if (matchingRuntimePacks.Count == 1)
            {
                return matchingRuntimePacks[0];
            }
            else
            {
                string runtimePackDescriptionForErrorMessage = knownFrameworkReference.RuntimeFrameworkName +
                    (requiredLabelsMetadata == string.Empty ? string.Empty : ":" + requiredLabelsMetadata);
 
                Log.LogError(Strings.ConflictingRuntimePackInformation, runtimePackDescriptionForErrorMessage,
                    string.Join(Environment.NewLine, matchingRuntimePacks.Select(rp => rp.RuntimePackNamePatterns)));
 
                return knownFrameworkReference.ToKnownRuntimePack();
            }
        }
 
        private void ProcessRuntimeIdentifier(
            string runtimeIdentifier,
            KnownRuntimePack selectedRuntimePack,
            string runtimePackVersion,
            List<string>? additionalFrameworkReferencesForRuntimePack,
            HashSet<string> unrecognizedRuntimeIdentifiers,
            List<ITaskItem> unavailableRuntimePacks,
            List<ITaskItem>? runtimePacks,
            List<ITaskItem> packagesToDownload,
            string? isTrimmable,
            bool addRuntimePackAndDownloadIfNecessary,
            bool wasReferencedDirectly)
        {
            var runtimeGraph = new RuntimeGraphCache(this).GetRuntimeGraph(RuntimeGraphPath);
            var knownFrameworkReferenceRuntimePackRuntimeIdentifiers = selectedRuntimePack.RuntimePackRuntimeIdentifiers.Split(';');
            var knownFrameworkReferenceRuntimePackExcludedRuntimeIdentifiers = selectedRuntimePack.RuntimePackExcludedRuntimeIdentifiers.Split(';');
            Log.LogMessage(MessageImportance.Low, $"Finding best RID match for pack {selectedRuntimePack.Name}@{runtimePackVersion} for target RID '{runtimeIdentifier}' from '{selectedRuntimePack.RuntimePackRuntimeIdentifiers}' excluding '{selectedRuntimePack.RuntimePackExcludedRuntimeIdentifiers}'");
            string? runtimePackRuntimeIdentifier = NuGetUtils.GetBestMatchingRidWithExclusion(
                    runtimeGraph,
                    runtimeIdentifier,
                    knownFrameworkReferenceRuntimePackExcludedRuntimeIdentifiers,
                    knownFrameworkReferenceRuntimePackRuntimeIdentifiers,
                    out bool wasInGraph);
 
            if (runtimePackRuntimeIdentifier == null)
            {
                if (wasInGraph)
                {
                    //  Report this as an error later, if necessary.  This is because we try to download
                    //  all available runtime packs in case there is a transitive reference to a shared
                    //  framework we don't directly reference.  But we don't want to immediately error out
                    //  here if a runtime pack that we might not need to reference isn't available for the
                    //  targeted RID (e.g. Microsoft.WindowsDesktop.App for a linux RID).
                    var unavailableRuntimePack = new TaskItem(selectedRuntimePack.Name);
                    unavailableRuntimePack.SetMetadata(MetadataKeys.RuntimeIdentifier, runtimeIdentifier);
                    unavailableRuntimePacks.Add(unavailableRuntimePack);
                }
                else if (!unrecognizedRuntimeIdentifiers.Contains(runtimeIdentifier))
                {
                    //  NETSDK1083: The specified RuntimeIdentifier '{0}' is not recognized.
                    Log.LogError(Strings.RuntimeIdentifierNotRecognized, runtimeIdentifier);
                    unrecognizedRuntimeIdentifiers.Add(runtimeIdentifier);
                }
            }
            else if (addRuntimePackAndDownloadIfNecessary)
            {
                foreach (var runtimePackNamePattern in selectedRuntimePack.RuntimePackNamePatterns.Split(';'))
                {
                    string runtimePackName = runtimePackNamePattern.Replace("**RID**", runtimePackRuntimeIdentifier);
 
                    //  Look up runtimePackVersion from workload manifests if necessary
                    string resolvedRuntimePackVersion = GetResolvedPackVersion(runtimePackName, runtimePackVersion);
 
                    string? runtimePackPath = GetPackPath(runtimePackName, resolvedRuntimePackVersion);
 
                    if (runtimePacks != null)
                    {
                        TaskItem runtimePackItem = new(runtimePackName);
                        runtimePackItem.SetMetadata(MetadataKeys.NuGetPackageId, runtimePackName);
                        runtimePackItem.SetMetadata(MetadataKeys.NuGetPackageVersion, resolvedRuntimePackVersion);
                        runtimePackItem.SetMetadata(MetadataKeys.FrameworkName, selectedRuntimePack.Name);
                        runtimePackItem.SetMetadata(MetadataKeys.RuntimeIdentifier, runtimePackRuntimeIdentifier);
                        runtimePackItem.SetMetadata(MetadataKeys.IsTrimmable, isTrimmable);
 
                        if (selectedRuntimePack.RuntimePackAlwaysCopyLocal)
                        {
                            runtimePackItem.SetMetadata(MetadataKeys.RuntimePackAlwaysCopyLocal, "true");
                        }
 
                        if (additionalFrameworkReferencesForRuntimePack != null)
                        {
                            runtimePackItem.SetMetadata(MetadataKeys.AdditionalFrameworkReferences, string.Join(";", additionalFrameworkReferencesForRuntimePack));
                        }
 
                        if (runtimePackPath != null)
                        {
                            runtimePackItem.SetMetadata(MetadataKeys.PackageDirectory, runtimePackPath);
                        }
 
                        runtimePacks.Add(runtimePackItem);
                    }
 
                    if (EnableRuntimePackDownload &&
                        runtimePackPath == null &&
                        (wasReferencedDirectly || !DisableTransitiveFrameworkReferenceDownloads))
                    {
                        TaskItem packageToDownload = new(runtimePackName);
                        packageToDownload.SetMetadata(MetadataKeys.Version, resolvedRuntimePackVersion);
 
                        packagesToDownload.Add(packageToDownload);
                    }
                }
            }
        }
 
        // Enum values should match the name of the pack: Known<Foo>Pack
        private enum ToolPackType
        {
            Crossgen2,
            ILCompiler,
            ILLink,
            WebAssemblySdk,
            AspNetCore,
        }
 
        enum ToolPackSupport
        {
            UnsupportedForTargetFramework,
            UnsupportedForHostRuntimeIdentifier,
            UnsupportedForTargetRuntimeIdentifier,
            Supported
        }
 
        private ToolPackSupport AddToolPack(
            ToolPackType toolPackType,
            Version normalizedTargetFrameworkVersion,
            List<ITaskItem> packagesToDownload,
            List<ITaskItem> implicitPackageReferences)
        {
            var knownPacks = toolPackType switch
            {
                ToolPackType.Crossgen2 => KnownCrossgen2Packs,
                ToolPackType.ILCompiler => KnownILCompilerPacks,
                ToolPackType.ILLink => KnownILLinkPacks,
                ToolPackType.WebAssemblySdk => KnownWebAssemblySdkPacks,
                ToolPackType.AspNetCore => KnownAspNetCorePacks,
                _ => throw new ArgumentException($"Unknown package type {toolPackType}", nameof(toolPackType))
            };
            Log.LogMessage(MessageImportance.Low, $"Adding tool pack {toolPackType} for runtime {normalizedTargetFrameworkVersion}");
 
            var knownPack = knownPacks.Where(pack =>
            {
                var packTargetFramework = NuGetFramework.Parse(pack.GetMetadata("TargetFramework"));
                return packTargetFramework.Framework.Equals(TargetFrameworkIdentifier, StringComparison.OrdinalIgnoreCase) &&
                    NormalizeVersion(packTargetFramework.Version) == normalizedTargetFrameworkVersion;
            }).SingleOrDefault();
 
            if (knownPack == null)
            {
                return ToolPackSupport.UnsupportedForTargetFramework;
            }
 
            var packName = toolPackType.ToString();
            var packVersion = knownPack.GetMetadata(packName + "PackVersion");
            if (!string.IsNullOrEmpty(RuntimeFrameworkVersion))
            {
                packVersion = RuntimeFrameworkVersion;
            }
 
            Log.LogMessage(MessageImportance.Low, $"Found {toolPackType} pack '{knownPack.ItemSpec}@{packVersion}'");
 
            TaskItem? runtimePackToDownload = null;
 
            // Crossgen and ILCompiler have RID-specific bits.
            if (toolPackType is ToolPackType.Crossgen2 or ToolPackType.ILCompiler)
            {
                var packNamePattern = knownPack.GetMetadata(packName + "PackNamePattern");
                var packSupportedRuntimeIdentifiers = knownPack.GetMetadata(packName + "RuntimeIdentifiers").Split(';');
                // When publishing for the non-portable RID that matches NETCoreSdkRuntimeIdentifier, prefer NETCoreSdkRuntimeIdentifier for the host.
                // Otherwise prefer the NETCoreSdkPortableRuntimeIdentifier.
                // This makes non-portable SDKs behave the same as portable SDKs except for the specific case of targetting the non-portable RID.
                // It also enables the non-portable ILCompiler to be packaged separately from the SDK and
                // only required when publishing for the non-portable SDK RID.
                string portableSdkRid = !string.IsNullOrEmpty(NETCoreSdkPortableRuntimeIdentifier) ? NETCoreSdkPortableRuntimeIdentifier : NETCoreSdkRuntimeIdentifier;
                bool targetsNonPortableSdkRid = EffectiveRuntimeIdentifier == NETCoreSdkRuntimeIdentifier && NETCoreSdkRuntimeIdentifier != portableSdkRid;
                string? hostRuntimeIdentifier = targetsNonPortableSdkRid ? NETCoreSdkRuntimeIdentifier : portableSdkRid;
                Log.LogMessage(MessageImportance.Low, $"Determining best RID for '{knownPack.ItemSpec}@{packVersion}' for '{hostRuntimeIdentifier}' from among '{knownPack.GetMetadata(packName + "RuntimeIdentifiers")}'");
                // Get the best RID for the host machine, which will be used to validate that we can run crossgen for the target platform and architecture
                var runtimeGraph = new RuntimeGraphCache(this).GetRuntimeGraph(RuntimeGraphPath);
                hostRuntimeIdentifier = NuGetUtils.GetBestMatchingRid(runtimeGraph, hostRuntimeIdentifier, packSupportedRuntimeIdentifiers, out bool wasInGraph);
                if (hostRuntimeIdentifier == null)
                {
                    return ToolPackSupport.UnsupportedForHostRuntimeIdentifier;
                }
                Log.LogMessage(MessageImportance.Low, $"Best RID for '{knownPack.ItemSpec}@{packVersion}' is '{hostRuntimeIdentifier}'");
                var runtimePackName = packNamePattern.Replace("**RID**", hostRuntimeIdentifier);
 
                var runtimePackItem = new TaskItem(runtimePackName);
                runtimePackItem.SetMetadata(MetadataKeys.NuGetPackageId, runtimePackName);
                runtimePackItem.SetMetadata(MetadataKeys.NuGetPackageVersion, packVersion);
 
                string? runtimePackPath = GetPackPath(runtimePackName, packVersion);
                if (runtimePackPath != null)
                {
                    runtimePackItem.SetMetadata(MetadataKeys.PackageDirectory, runtimePackPath);
                }
                else if (EnableRuntimePackDownload)
                {
                    // We need to download the runtime pack
                    runtimePackToDownload = new TaskItem(runtimePackName);
                    runtimePackToDownload.SetMetadata(MetadataKeys.Version, packVersion);
                }
 
                runtimePackItem.SetMetadata(MetadataKeys.RuntimeIdentifier, hostRuntimeIdentifier);
                Log.LogMessage(MessageImportance.Low, $"Added {toolPackType} runtime pack '{runtimePackName}@{packVersion}'");
 
                switch (toolPackType)
                {
                    case ToolPackType.Crossgen2:
                        Crossgen2Packs = new[] { runtimePackItem };
                        break;
                    case ToolPackType.ILCompiler:
                        // ILCompiler supports cross target compilation. If there is a cross-target request,
                        // we need to download that package as well unless we use KnownRuntimePack entries for the target.
                        // We expect RuntimeIdentifier to be defined during publish but can allow during build
                        if (EffectiveRuntimeIdentifier != null && !AotUseKnownRuntimePackForTarget)
                        {
                            Log.LogMessage(MessageImportance.Low, $"Checking for cross-targeting compilation packs");
                            var targetRuntimeIdentifier = NuGetUtils.GetBestMatchingRid(runtimeGraph, EffectiveRuntimeIdentifier, packSupportedRuntimeIdentifiers, out bool wasInGraph2);
                            if (targetRuntimeIdentifier == null)
                            {
                                return ToolPackSupport.UnsupportedForTargetRuntimeIdentifier;
                            }
 
                            // If there's an available runtime pack, use it instead of the ILCompiler package for target-specific bits.
                            bool useRuntimePackForAllTargets = false;
                            string targetPackNamePattern = packNamePattern;
                            if (knownPack.GetMetadata("ILCompilerRuntimePackNamePattern") is string runtimePackNamePattern && runtimePackNamePattern != string.Empty)
                            {
                                targetPackNamePattern = runtimePackNamePattern;
                                useRuntimePackForAllTargets = true;
                            }
 
                            if (useRuntimePackForAllTargets || !hostRuntimeIdentifier.Equals(targetRuntimeIdentifier))
                            {
                                var targetIlcPackName = targetPackNamePattern.Replace("**RID**", targetRuntimeIdentifier);
                                var targetIlcPack = new TaskItem(targetIlcPackName);
                                targetIlcPack.SetMetadata(MetadataKeys.NuGetPackageId, targetIlcPackName);
                                targetIlcPack.SetMetadata(MetadataKeys.NuGetPackageVersion, packVersion);
                                TargetILCompilerPacks = new[] { targetIlcPack };
                                Log.LogMessage(MessageImportance.Low, $"Added {targetIlcPackName}@{packVersion} for cross-targeting compilation");
 
                                string? targetILCompilerPackPath = GetPackPath(targetIlcPackName, packVersion);
                                if (targetILCompilerPackPath != null)
                                {
                                    targetIlcPack.SetMetadata(MetadataKeys.PackageDirectory, targetILCompilerPackPath);
                                }
                                else if (EnableRuntimePackDownload)
                                {
                                    // We need to download the runtime pack
                                    var targetIlcPackToDownload = new TaskItem(targetIlcPackName);
                                    targetIlcPackToDownload.SetMetadata(MetadataKeys.Version, packVersion);
                                    packagesToDownload.Add(targetIlcPackToDownload);
                                }
                            }
                            else
                            {
                                Log.LogMessage(MessageImportance.Low, $"No cross-targeting compilation packs required.");
                            }
                        }
 
                        HostILCompilerPacks = new[] { runtimePackItem };
                        break;
                }
            }
 
            if (runtimePackToDownload != null)
            {
                packagesToDownload.Add(runtimePackToDownload);
            }
 
            if (toolPackType is ToolPackType.ILLink)
            {
                // The ILLink tool pack is available for some TargetFrameworks where we nonetheless consider
                // IsTrimmable/IsAotCompatible/EnableSingleFile to be unsupported, because the framework
                // was not annotated with the attributes.
                if (FirstTargetFrameworkVersionToSupportAotAnalyzer != null)
                {
                    var firstTargetFrameworkVersionToSupportAotAnalyzer = NormalizeVersion(new Version(FirstTargetFrameworkVersionToSupportAotAnalyzer));
                    if ((IsAotCompatible || EnableAotAnalyzer) && normalizedTargetFrameworkVersion < firstTargetFrameworkVersionToSupportAotAnalyzer)
                    {
                        return ToolPackSupport.UnsupportedForTargetFramework;
                    }
                }
                if (FirstTargetFrameworkVersionToSupportSingleFileAnalyzer != null)
                {
                    var firstTargetFrameworkVersionToSupportSingleFileAnalyzer = NormalizeVersion(new Version(FirstTargetFrameworkVersionToSupportSingleFileAnalyzer));
                    if (EnableSingleFileAnalyzer && normalizedTargetFrameworkVersion < firstTargetFrameworkVersionToSupportSingleFileAnalyzer)
                    {
                        return ToolPackSupport.UnsupportedForTargetFramework;
                    }
                }
                if (FirstTargetFrameworkVersionToSupportTrimAnalyzer != null)
                {
                    var firstTargetFrameworkVersionToSupportTrimAnalyzer = NormalizeVersion(new Version(FirstTargetFrameworkVersionToSupportTrimAnalyzer));
                    if ((IsTrimmable || EnableTrimAnalyzer) && normalizedTargetFrameworkVersion < firstTargetFrameworkVersionToSupportTrimAnalyzer)
                    {
                        return ToolPackSupport.UnsupportedForTargetFramework;
                    }
                }
            }
 
            // Packs with RID-agnostic build packages that contain MSBuild targets.
            if (toolPackType is not ToolPackType.Crossgen2 && EnableRuntimePackDownload)
            {
                Log.LogMessage(MessageImportance.Low, $"Added {knownPack.ItemSpec}@{packVersion} for build-time targets");
                var buildPackageName = knownPack.ItemSpec;
                var buildPackage = new TaskItem(buildPackageName);
                buildPackage.SetMetadata(MetadataKeys.Version, packVersion);
                implicitPackageReferences.Add(buildPackage);
            }
 
            // Before net8.0, ILLink analyzers shipped in a separate package.
            // Add the analyzer package with version taken from KnownILLinkPack if the version is less than 8.0.0.
            // The version comparison doesn't consider prerelease labels, so 8.0.0-foo will be considered equal to 8.0.0 and
            // will not get the extra analyzer package reference.
            if (toolPackType is ToolPackType.ILLink &&
                new VersionComparer(VersionComparison.Version).Compare(NuGetVersion.Parse(packVersion), new NuGetVersion(8, 0, 0)) < 0 &&
                EnableRuntimePackDownload)
            {
                var analyzerPackage = new TaskItem("Microsoft.NET.ILLink.Analyzers");
                analyzerPackage.SetMetadata(MetadataKeys.Version, packVersion);
                implicitPackageReferences.Add(analyzerPackage);
                Log.LogMessage(MessageImportance.Low, $"Added {analyzerPackage.ItemSpec}@{packVersion} for linker analyzers");
            }
 
            return ToolPackSupport.Supported;
        }
 
        private string GetRuntimeFrameworkVersion(
            ITaskItem? frameworkReference,
            KnownFrameworkReference knownFrameworkReference,
            KnownRuntimePack? knownRuntimePack,
            out string runtimePackVersion)
        {
            //  Precedence order for selecting runtime framework version
            //  - RuntimeFrameworkVersion metadata on FrameworkReference item
            //  - RuntimeFrameworkVersion MSBuild property
            //  - Then, use either the LatestRuntimeFrameworkVersion or the DefaultRuntimeFrameworkVersion of the KnownFrameworkReference, based on
            //      - The value (if set) of TargetLatestRuntimePatch metadata on the FrameworkReference
            //      - The TargetLatestRuntimePatch MSBuild property (which defaults to True if SelfContained is true, and False otherwise)
            //      - But, if TargetLatestRuntimePatch was defaulted and not overridden by user, then acquire latest runtime pack for future
            //        self-contained deployment (or for crossgen of framework-dependent deployment), while targeting the default version.
 
            string? requestedVersion = GetRequestedRuntimeFrameworkVersion(frameworkReference);
            if (!string.IsNullOrEmpty(requestedVersion))
            {
                runtimePackVersion = requestedVersion;
                return requestedVersion;
            }
 
            switch (GetRuntimePatchRequest(frameworkReference))
            {
                case RuntimePatchRequest.UseDefaultVersion:
                    runtimePackVersion = knownFrameworkReference.DefaultRuntimeFrameworkVersion;
                    return knownFrameworkReference.DefaultRuntimeFrameworkVersion;
 
                case RuntimePatchRequest.UseLatestVersion:
                    if (knownRuntimePack is KnownRuntimePack knownPack)
                    {
                        runtimePackVersion = knownPack.LatestRuntimeFrameworkVersion;
                        return knownPack.LatestRuntimeFrameworkVersion;
                    }
                    else
                    {
                        runtimePackVersion = knownFrameworkReference.DefaultRuntimeFrameworkVersion;
                        return knownFrameworkReference.DefaultRuntimeFrameworkVersion;
                    }
                case RuntimePatchRequest.UseDefaultVersionWithLatestRuntimePack:
                    if (knownRuntimePack is KnownRuntimePack knownPack2)
                    {
                        runtimePackVersion = knownPack2.LatestRuntimeFrameworkVersion;
                    }
                    else
                    {
                        runtimePackVersion = knownFrameworkReference.DefaultRuntimeFrameworkVersion;
                    }
                    return knownFrameworkReference.DefaultRuntimeFrameworkVersion;
 
                default:
                    // Unreachable
                    throw new InvalidOperationException();
            }
        }
 
        private string? GetPackPath(string packName, string packVersion)
        {
            IEnumerable<string> GetPackFolders()
            {
                var packRootEnvironmentVariable = Environment.GetEnvironmentVariable(EnvironmentVariableNames.WORKLOAD_PACK_ROOTS);
                if (!string.IsNullOrEmpty(packRootEnvironmentVariable))
                {
                    foreach (var packRoot in packRootEnvironmentVariable.Split(Path.PathSeparator))
                    {
                        yield return Path.Combine(packRoot, "packs");
                    }
                }
 
                if (!string.IsNullOrEmpty(NetCoreRoot) && !string.IsNullOrEmpty(NETCoreSdkVersion))
                {
                    if (WorkloadFileBasedInstall.IsUserLocal(NetCoreRoot, NETCoreSdkVersion) &&
                        CliFolderPathCalculatorCore.GetDotnetUserProfileFolderPath() is { } userProfileDir)
                    {
                        yield return Path.Combine(userProfileDir, "packs");
                    }
                }
 
                if (!string.IsNullOrEmpty(TargetingPackRoot))
                {
                    yield return TargetingPackRoot;
                }
            }
 
            foreach (var packFolder in GetPackFolders())
            {
                string packPath = Path.Combine(packFolder, packName, packVersion);
                if (Directory.Exists(packPath))
                {
                    return packPath;
                }
            }
 
            return null;
        }
 
        Lazy<WorkloadResolver> _workloadResolver
        {
            get
            {
                field ??= LazyCreateWorkloadResolver();
                return field;
             }
        }
 
        private string GetResolvedPackVersion(string packID, string packVersion)
        {
            if (!packVersion.Equals("**FromWorkload**", StringComparison.OrdinalIgnoreCase))
            {
                return packVersion;
            }
 
            var packInfo = _workloadResolver.Value.TryGetPackInfo(new WorkloadPackId(packID));
            if (packInfo == null)
            {
                Log.LogError(Strings.CouldNotGetPackVersionFromWorkloadManifests, packID);
                return packVersion;
            }
            return packInfo.Version;
        }
 
        private Lazy<WorkloadResolver> LazyCreateWorkloadResolver()
        {
            return new(() =>
        {
                string? userProfileDir = CliFolderPathCalculatorCore.GetDotnetUserProfileFolderPath();
 
                //  When running MSBuild tasks, the current directory is always the project directory, so we can use that as the
                //  starting point to search for global.json
                string? globalJsonPath = SdkDirectoryWorkloadManifestProvider.GetGlobalJsonPath(Environment.CurrentDirectory);
 
                var manifestProvider = new SdkDirectoryWorkloadManifestProvider(NetCoreRoot, NETCoreSdkVersion, userProfileDir, globalJsonPath);
                return WorkloadResolver.Create(manifestProvider, NetCoreRoot, NETCoreSdkVersion, userProfileDir);
        });
        }
 
        private enum RuntimePatchRequest
        {
            UseDefaultVersionWithLatestRuntimePack,
            UseDefaultVersion,
            UseLatestVersion,
        }
 
        /// <summary>
        /// Compare PackageToDownload by name and version.
        /// Used to deduplicate PackageToDownloads
        /// </summary>
        private class PackageToDownloadComparer<T> : IEqualityComparer<T> where T : ITaskItem
        {
            public bool Equals(T? x, T? y)
            {
                if (x is null || y is null)
                {
                    return false;
                }
 
                return x.ItemSpec.Equals(y.ItemSpec,
                           StringComparison.OrdinalIgnoreCase) &&
                       x.GetMetadata(MetadataKeys.Version).Equals(
                           y.GetMetadata(MetadataKeys.Version), StringComparison.OrdinalIgnoreCase);
            }
 
            public int GetHashCode(T obj)
            {
                var hashCode = -1923861349;
                hashCode = hashCode * -1521134295 + obj.ItemSpec.GetHashCode();
                hashCode = hashCode * -1521134295 + obj.GetMetadata(MetadataKeys.Version).GetHashCode();
                return hashCode;
            }
        }
 
        private RuntimePatchRequest GetRuntimePatchRequest(ITaskItem? frameworkReference)
        {
            string? value = frameworkReference?.GetMetadata("TargetLatestRuntimePatch");
            if (!string.IsNullOrEmpty(value))
            {
                return MSBuildUtilities.ConvertStringToBool(value, defaultValue: false)
                    ? RuntimePatchRequest.UseLatestVersion
                    : RuntimePatchRequest.UseDefaultVersion;
            }
 
            if (TargetLatestRuntimePatch)
            {
                return RuntimePatchRequest.UseLatestVersion;
            }
 
            return TargetLatestRuntimePatchIsDefault
                ? RuntimePatchRequest.UseDefaultVersionWithLatestRuntimePack
                : RuntimePatchRequest.UseDefaultVersion;
        }
 
        private string? GetRequestedRuntimeFrameworkVersion(ITaskItem? frameworkReference)
        {
            string? requestedVersion = frameworkReference?.GetMetadata("RuntimeFrameworkVersion");
 
            if (string.IsNullOrEmpty(requestedVersion))
            {
                requestedVersion = RuntimeFrameworkVersion;
            }
 
            return requestedVersion;
        }
 
        internal static Version NormalizeVersion(Version version)
        {
            if (version.Revision == 0)
            {
                if (version.Build == 0)
                {
                    return new Version(version.Major, version.Minor);
                }
                else
                {
                    return new Version(version.Major, version.Minor, version.Build);
                }
            }
 
            return version;
        }
 
        private struct KnownFrameworkReference
        {
            ITaskItem _item;
            public KnownFrameworkReference(ITaskItem item)
            {
                _item = item;
                TargetFramework = NuGetFramework.Parse(item.GetMetadata("TargetFramework"));
            }
 
            //  The name / itemspec of the FrameworkReference used in the project
            public string Name => _item.ItemSpec;
 
            //  The framework name to write to the runtimeconfig file (and the name of the folder under dotnet/shared)
            public string RuntimeFrameworkName => _item.GetMetadata(MetadataKeys.RuntimeFrameworkName);
            public string DefaultRuntimeFrameworkVersion => _item.GetMetadata("DefaultRuntimeFrameworkVersion");
 
            //  The ID of the targeting pack NuGet package to reference
            public string TargetingPackName => _item.GetMetadata("TargetingPackName");
            public string TargetingPackVersion => _item.GetMetadata("TargetingPackVersion");
            public string TargetingPackFormat => _item.GetMetadata("TargetingPackFormat");
 
            public string RuntimePackRuntimeIdentifiers => _item.GetMetadata(MetadataKeys.RuntimePackRuntimeIdentifiers);
 
            public bool IsWindowsOnly => _item.HasMetadataValue("IsWindowsOnly", "true");
 
            public bool RuntimePackAlwaysCopyLocal =>
                _item.HasMetadataValue(MetadataKeys.RuntimePackAlwaysCopyLocal, "true");
 
            public string Profile => _item.GetMetadata("Profile");
 
            public NuGetFramework TargetFramework { get; }
 
            public KnownRuntimePack ToKnownRuntimePack()
            {
                return new KnownRuntimePack(_item);
            }
        }
 
        private struct KnownRuntimePack
        {
            ITaskItem _item;
 
            public KnownRuntimePack(ITaskItem item)
            {
                _item = item;
                TargetFramework = NuGetFramework.Parse(item.GetMetadata("TargetFramework"));
                string runtimePackLabels = item.GetMetadata(MetadataKeys.RuntimePackLabels);
                if (string.IsNullOrEmpty(runtimePackLabels))
                {
                    RuntimePackLabels = Array.Empty<string>();
                }
                else
                {
                    RuntimePackLabels = runtimePackLabels.Split(';');
                }
            }
 
            //  The name / itemspec of the FrameworkReference used in the project
            public string Name => _item.ItemSpec;
 
            ////  The framework name to write to the runtimeconfig file (and the name of the folder under dotnet/shared)
            public string LatestRuntimeFrameworkVersion => _item.GetMetadata("LatestRuntimeFrameworkVersion");
 
            public string RuntimePackNamePatterns => _item.GetMetadata("RuntimePackNamePatterns");
 
            public string RuntimePackRuntimeIdentifiers => _item.GetMetadata(MetadataKeys.RuntimePackRuntimeIdentifiers);
 
            public string RuntimePackExcludedRuntimeIdentifiers => _item.GetMetadata(MetadataKeys.RuntimePackExcludedRuntimeIdentifiers);
 
            public string IsTrimmable => _item.GetMetadata(MetadataKeys.IsTrimmable);
 
            public bool IsWindowsOnly => _item.HasMetadataValue("IsWindowsOnly", "true");
 
            public bool RuntimePackAlwaysCopyLocal =>
                _item.HasMetadataValue(MetadataKeys.RuntimePackAlwaysCopyLocal, "true");
 
            public string[] RuntimePackLabels { get; }
 
            public NuGetFramework TargetFramework { get; }
        }
    }
}