File: RestoreCommand\Utility\MSBuildRestoreUtility.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.Collections.Immutable;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using NuGet.Common;
using NuGet.Configuration;
using NuGet.Frameworks;
using NuGet.LibraryModel;
using NuGet.Packaging;
using NuGet.ProjectModel;
using NuGet.RuntimeModel;
using NuGet.Shared;
using NuGet.Versioning;

namespace NuGet.Commands
{
    /// <summary>
    /// Helpers for dealing with dg files and processing msbuild related inputs.
    /// </summary>
    public static class MSBuildRestoreUtility
    {
        // Clear keyword for properties.
        public static readonly string Clear = nameof(Clear);
        private static readonly string[] HttpPrefixes = new string[] { "http:", "https:" };
        private const string DoubleSlash = "//";

        /// <summary>
        /// Creates a <see cref="DependencyGraphSpec" /> from the specified MSBuild items.
        /// </summary>
        /// <param name="items">An <see cref="IEnumerable{T}" /> of <see cref="IMSBuildItem" /> objects representing the MSBuild items gathered for restore.</param>
        public static DependencyGraphSpec GetDependencySpec(IEnumerable<IMSBuildItem> items)
        {
            return GetDependencySpec(items, readOnly: false);
        }
        /// <summary>
        /// Creates a <see cref="DependencyGraphSpec" /> from the specified MSBuild items.
        /// </summary>
        /// <param name="items">An <see cref="IEnumerable{T}" /> of <see cref="IMSBuildItem" /> objects representing the MSBuild items gathered for restore.</param>
        /// <param name="readOnly"><see langword="true" /> to indicate that the <see cref="DependencyGraphSpec" /> is considered read-only and won't be changed, otherwise <see langword="false" />.</param>
        public static DependencyGraphSpec GetDependencySpec(IEnumerable<IMSBuildItem> items, bool readOnly)
        {
            if (items == null)
            {
                throw new ArgumentNullException(nameof(items));
            }

            // Unique names created by the MSBuild restore target are project paths, these
            // can be different on case-insensitive file systems for the same project file.
            // To workaround this unique names should be compared based on the OS.
            var uniqueNameComparer = PathUtility.GetStringComparerBasedOnOS();

            var graphSpec = new DependencyGraphSpec(readOnly);
            var itemsById = new Dictionary<string, List<IMSBuildItem>>(uniqueNameComparer);
            var restoreSpecs = new HashSet<string>(uniqueNameComparer);
            var validForRestore = new HashSet<string>(uniqueNameComparer);
            var projectPathLookup = new Dictionary<string, string>(uniqueNameComparer);

            // Sort items and add restore specs
            foreach (var item in items)
            {
                var projectUniqueName = item.GetProperty("ProjectUniqueName");

                if (item.IsType("restorespec"))
                {
                    restoreSpecs.Add(projectUniqueName);
                }
                else if (!string.IsNullOrEmpty(projectUniqueName))
                {
                    List<IMSBuildItem> idItems;
                    if (!itemsById.TryGetValue(projectUniqueName, out idItems))
                    {
                        idItems = new List<IMSBuildItem>(1);
                        itemsById.Add(projectUniqueName, idItems);
                    }

                    idItems.Add(item);
                }
            }

            // Add projects
            var validProjectSpecs = itemsById.Values.Select(GetPackageSpec).Where(e => e != null);

            foreach (var spec in validProjectSpecs)
            {
                // Keep track of all project path casings
                var uniqueName = spec.RestoreMetadata.ProjectUniqueName;
                if (uniqueName != null && !projectPathLookup.ContainsKey(uniqueName))
                {
                    projectPathLookup.Add(uniqueName, uniqueName);
                }

                var projectPath = spec.RestoreMetadata.ProjectPath;
                if (projectPath != null && !projectPathLookup.ContainsKey(projectPath))
                {
                    projectPathLookup.Add(projectPath, projectPath);
                }

                if (spec.RestoreMetadata.ProjectStyle == ProjectStyle.PackageReference
                    || spec.RestoreMetadata.ProjectStyle == ProjectStyle.DotnetCliTool)
                {
                    validForRestore.Add(spec.RestoreMetadata.ProjectUniqueName);
                }

                graphSpec.AddProject(spec);
            }

            // Fix project reference casings to match the original project on case insensitive file systems.
            NormalizePathCasings(projectPathLookup, graphSpec);

            // Remove references to projects that could not be read by restore.
            RemoveMissingProjects(graphSpec);

            // Add valid projects to restore section
            foreach (var projectUniqueName in restoreSpecs.Intersect(validForRestore))
            {
                graphSpec.AddRestore(projectUniqueName);
            }

            return graphSpec;
        }

        /// <summary>
        /// Insert asset flags into dependency, based on ;-delimited string args
        /// </summary>
        public static (LibraryIncludeFlags includeType, LibraryIncludeFlags suppressParent) GetLibraryDependencyIncludeFlags(string includeAssets, string excludeAssets, string privateAssets)
        {
            var includeFlags = GetIncludeFlags(includeAssets, LibraryIncludeFlags.All);
            var excludeFlags = GetIncludeFlags(excludeAssets, LibraryIncludeFlags.None);

            var includeType = includeFlags & ~excludeFlags;
            var suppressParent = GetIncludeFlags(privateAssets, LibraryIncludeFlagUtils.DefaultSuppressParent);

            return (includeType, suppressParent);
        }

        /// <summary>
        /// Insert asset flags into project dependency, based on ;-delimited string args
        /// </summary>
        public static void ApplyIncludeFlags(ProjectRestoreReference dependency, string includeAssets, string excludeAssets, string privateAssets)
        {
            if (dependency == null) throw new ArgumentNullException(nameof(dependency));
            dependency.IncludeAssets = GetIncludeFlags(includeAssets, LibraryIncludeFlags.All);
            dependency.ExcludeAssets = GetIncludeFlags(excludeAssets, LibraryIncludeFlags.None);
            dependency.PrivateAssets = GetIncludeFlags(privateAssets, LibraryIncludeFlagUtils.DefaultSuppressParent);
        }

        /// <summary>
        /// Convert MSBuild items to a PackageSpec.
        /// </summary>
        public static PackageSpec GetPackageSpec(IEnumerable<IMSBuildItem> items)
        {
            if (items == null)
            {
                throw new ArgumentNullException(nameof(items));
            }

            PackageSpec result = null;

            // There should only be one ProjectSpec per project in the item set,
            // but if multiple do appear take only the first one in an effort
            // to handle this gracefully.
            var specItem = GetItemByType(items, "projectSpec").FirstOrDefault();

            if (specItem != null)
            {
                ProjectStyle restoreType = GetProjectStyle(specItem);

                (bool isCentralPackageManagementEnabled, bool isCentralPackageVersionOverrideDisabled, bool isCentralPackageTransitivePinningEnabled, bool isCentralPackageFloatingVersionsEnabled) = GetCentralPackageManagementSettings(specItem, restoreType);

                // Read msbuild data for PR and related projects
                result = GetBaseSpec(specItem, restoreType, items);

                // Applies to all types
                result.RestoreMetadata.ProjectStyle = restoreType;
                result.RestoreMetadata.ProjectPath = specItem.GetProperty("ProjectPath");
                result.RestoreMetadata.ProjectUniqueName = specItem.GetProperty("ProjectUniqueName");

                if (string.IsNullOrEmpty(result.Name))
                {
                    result.Name = result.RestoreMetadata.ProjectName
                        ?? result.RestoreMetadata.ProjectUniqueName
                        ?? Path.GetFileNameWithoutExtension(result.FilePath);
                }

                // Read project references for all
                AddProjectReferences(result, items);

                if (restoreType == ProjectStyle.PackageReference
                    || restoreType == ProjectStyle.DotnetCliTool
                    || restoreType == ProjectStyle.PackagesConfig)
                {

                    foreach (var source in MSBuildStringUtility.Split(specItem.GetProperty("Sources")))
                    {
                        // Fix slashes incorrectly removed by MSBuild
                        var pkgSource = new PackageSource(FixSourcePath(source));
                        result.RestoreMetadata.Sources.Add(pkgSource);
                    }

                    foreach (var configFilePath in MSBuildStringUtility.Split(specItem.GetProperty("ConfigFilePaths")))
                    {
                        result.RestoreMetadata.ConfigFilePaths.Add(configFilePath);
                    }

                    foreach (var folder in MSBuildStringUtility.Split(specItem.GetProperty("FallbackFolders")))
                    {
                        result.RestoreMetadata.FallbackFolders.Add(folder);
                    }

                    result.RestoreMetadata.PackagesPath = specItem.GetProperty("PackagesPath");
                    result.RestoreMetadata.OutputPath = specItem.GetProperty("OutputPath");
                }

                // Read package references for netcore, tools, and standalone
                if (restoreType == ProjectStyle.PackageReference
                    || restoreType == ProjectStyle.DotnetCliTool)
                {
                    AddPackageReferences(result, items, isCentralPackageManagementEnabled);
                    AddPackageDownloads(result, items);
                    AddFrameworkReferences(result, items);
                    AddPrunePackageReferences(result, items);

                    // Store the original framework strings for msbuild conditionals
                    result.TargetFrameworks.ForEach(tfi =>
                        result.RestoreMetadata.OriginalTargetFrameworks.Add(
                                !string.IsNullOrEmpty(tfi.TargetAlias) ?
                                    tfi.TargetAlias :
                                    tfi.FrameworkName.GetShortFolderName()));
                }

                if (restoreType == ProjectStyle.PackageReference)
                {
                    // Set project version
                    result.Version = GetVersion(specItem);

                    // Add RIDs and Supports
                    result.RuntimeGraph = GetRuntimeGraph(specItem);

                    // Add CrossTargeting flag
                    result.RestoreMetadata.CrossTargeting = IsPropertyTrue(specItem, "CrossTargeting");

                    // Add RestoreLegacyPackagesDirectory flag
                    result.RestoreMetadata.LegacyPackagesDirectory = IsPropertyTrue(
                        specItem,
                        "RestoreLegacyPackagesDirectory");

                    // ValidateRuntimeAssets compat check
                    result.RestoreMetadata.ValidateRuntimeAssets = IsPropertyTrue(specItem, "ValidateRuntimeAssets");

                    // True for .NETCore projects.
                    result.RestoreMetadata.SkipContentFileWrite = IsPropertyTrue(specItem, "SkipContentFileWrite");

                    // Warning properties
                    result.RestoreMetadata.ProjectWideWarningProperties = GetWarningProperties(specItem);

                    // Packages lock file properties
                    result.RestoreMetadata.RestoreLockProperties = GetRestoreLockProperties(specItem);

                    // NuGet audit properties
                    result.RestoreMetadata.RestoreAuditProperties = GetRestoreAuditProperties(specItem, items, GetAuditSuppressions(items));
                }

                if (restoreType == ProjectStyle.PackagesConfig)
                {
                    var pcRestoreMetadata = (PackagesConfigProjectRestoreMetadata)result.RestoreMetadata;
                    // Packages lock file properties
                    pcRestoreMetadata.PackagesConfigPath = specItem.GetProperty("PackagesConfigPath");
                    pcRestoreMetadata.RepositoryPath = specItem.GetProperty("RepositoryPath");
                    var solutionDir = specItem.GetProperty("SolutionDir");
                    if (string.IsNullOrEmpty(pcRestoreMetadata.RepositoryPath) && !string.IsNullOrEmpty(solutionDir) && solutionDir != "*Undefined*")
                    {
                        pcRestoreMetadata.RepositoryPath = Path.Combine(
                            solutionDir,
                            "packages"
                        );
                    }
                    pcRestoreMetadata.RestoreLockProperties = GetRestoreLockProperties(specItem);
                    pcRestoreMetadata.RestoreAuditProperties = GetRestoreAuditProperties(specItem, items, GetAuditSuppressions(items));
                }

                result.RestoreMetadata.CentralPackageVersionsEnabled = isCentralPackageManagementEnabled;
                result.RestoreMetadata.CentralPackageVersionOverrideDisabled = isCentralPackageVersionOverrideDisabled;
                result.RestoreMetadata.CentralPackageFloatingVersionsEnabled = isCentralPackageFloatingVersionsEnabled;
                result.RestoreMetadata.CentralPackageTransitivePinningEnabled = isCentralPackageTransitivePinningEnabled;
                result.RestoreMetadata.UsingMicrosoftNETSdk = GetUsingMicrosoftNETSdk(specItem.GetProperty("UsingMicrosoftNETSdk"));
                result.RestoreMetadata.SdkAnalysisLevel = GetSdkAnalysisLevel(specItem.GetProperty("SdkAnalysisLevel"));
                result.RestoreMetadata.UseLegacyDependencyResolver = IsPropertyTrue(specItem, "RestoreUseLegacyDependencyResolver");
                result.RestoreMetadata.RestoreDoNotWriteDependencyGraphSpec = IsPropertyTrue(specItem, "RestoreDoNotWriteDependencyGraphSpec");
                result.RestoreSettings.SdkVersion = GetSdkAnalysisLevel(specItem.GetProperty("NETCoreSdkVersion"));
            }

            return result;
        }

        /// <summary>
        /// Remove missing project dependencies. These are typically caused by
        /// non-NuGet projects which are missing the targets needed to walk them.
        /// Visual Studio ignores these projects so from the command line we should
        /// also. Build will fail with the appropriate errors for missing projects
        /// restore should not warn or message for this.
        /// </summary>
        public static void RemoveMissingProjects(DependencyGraphSpec graphSpec)
        {
            var existingProjects = new HashSet<string>(
                graphSpec.Projects.Select(e => e.RestoreMetadata.ProjectPath),
                PathUtility.GetStringComparerBasedOnOS());

            foreach (var project in graphSpec.Projects)
            {
                foreach (var framework in project.RestoreMetadata.TargetFrameworks)
                {
                    // Loop through the items in reverse order so items can be removed from the collection safely
                    for (int i = framework.ProjectReferences.Count - 1; i >= 0; i--)
                    {
                        if (!existingProjects.Contains(framework.ProjectReferences[i].ProjectPath))
                        {
                            framework.ProjectReferences.Remove(framework.ProjectReferences[i]);
                        }
                    }
                }
            }
        }

        public static void NormalizePathCasings(Dictionary<string, string> paths, DependencyGraphSpec graphSpec)
        {
            NormalizePathCasings((IDictionary<string, string>)paths, graphSpec);
        }

        /// <summary>
        /// Change all project paths to the same casing.
        /// </summary>
        public static void NormalizePathCasings(IDictionary<string, string> paths, DependencyGraphSpec graphSpec)
        {
            if (PathUtility.IsFileSystemCaseInsensitive)
            {
                foreach (var project in graphSpec.Projects)
                {
                    foreach (var framework in project.RestoreMetadata.TargetFrameworks)
                    {
                        foreach (var projectReference in framework.ProjectReferences)
                        {
                            // Check reference unique name
                            var refUniqueName = projectReference.ProjectUniqueName;

                            if (refUniqueName != null
                                && paths.TryGetValue(refUniqueName, out var refUniqueNameCasing)
                                && !StringComparer.Ordinal.Equals(refUniqueNameCasing, refUniqueName))
                            {
                                projectReference.ProjectUniqueName = refUniqueNameCasing;
                            }

                            // Check reference project path
                            var projectRefPath = projectReference.ProjectPath;

                            if (projectRefPath != null
                                && paths.TryGetValue(projectRefPath, out var projectRefPathCasing)
                                && !StringComparer.Ordinal.Equals(projectRefPathCasing, projectRefPath))
                            {
                                projectReference.ProjectPath = projectRefPathCasing;
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// True if the list contains CLEAR.
        /// </summary>
        public static bool ContainsClearKeyword(IEnumerable<string> values)
        {
            return (values?.Contains(Clear, StringComparer.OrdinalIgnoreCase) == true);
        }

        /// <summary>
        /// True if the list contains CLEAR and non-CLEAR keywords.
        /// </summary>
        /// <remarks>CLEAR;CLEAR is considered valid.</remarks>
        public static bool HasInvalidClear(IEnumerable<string> values)
        {
            return ContainsClearKeyword(values)
                    && (values?.Any(e => !StringComparer.OrdinalIgnoreCase.Equals(e, Clear)) == true);
        }

        /// <summary>
        /// Logs an error if CLEAR is used with non-CLEAR entries.
        /// </summary>
        /// <returns>True if an invalid combination exists.</returns>
        public static bool LogErrorForClearIfInvalid(IEnumerable<string> values, string projectPath, ILogger logger)
        {
            if (HasInvalidClear(values))
            {
                var text = string.Format(CultureInfo.CurrentCulture, Strings.CannotBeUsedWithOtherValues, Clear);
                var message = LogMessage.CreateError(NuGetLogCode.NU1002, text);
                message.ProjectPath = projectPath;
                logger.Log(message);

                return true;
            }

            return false;
        }

        /// <summary>
        /// Log warning NU1503
        /// </summary>
        public static RestoreLogMessage GetWarningForUnsupportedProject(string path)
        {
            var text = string.Format(CultureInfo.CurrentCulture, Strings.UnsupportedProject, path);
            var message = RestoreLogMessage.CreateWarning(NuGetLogCode.NU1503, text);
            message.FilePath = path;
            message.ProjectPath = path;

            return message;
        }

        public static RestoreLogMessage GetMessageForUnsupportedProject(string path)
        {
            var text = string.Format(CultureInfo.CurrentCulture, Strings.UnsupportedProject, path);
            var message = new RestoreLogMessage(LogLevel.Information, text);
            message.FilePath = path;
            message.ProjectPath = path;

            return message;
        }

        private static IEnumerable<TargetFrameworkInformation> GetTargetFrameworkInformation(string filePath, ProjectStyle restoreType, IEnumerable<IMSBuildItem> items)
        {
            var uniqueIds = new HashSet<string>();
            foreach (var item in GetItemByType(items, "TargetFrameworkInformation"))
            {
                var frameworkString = item.GetProperty("TargetFramework");
                var targetFrameworkMoniker = item.GetProperty("TargetFrameworkMoniker");
                var targetPlatforMoniker = item.GetProperty("TargetPlatformMoniker");
                var targetPlatformMinVersion = item.GetProperty("TargetPlatformMinVersion");
                var clrSupport = item.GetProperty("CLRSupport");
                var windowsTargetPlatformMinVersion = item.GetProperty("WindowsTargetPlatformMinVersion");
                var targetAlias = string.IsNullOrEmpty(frameworkString) ? string.Empty : frameworkString;
                if (uniqueIds.Contains(targetAlias))
                {
                    continue;
                }
                uniqueIds.Add(targetAlias);

                NuGetFramework targetFramework = MSBuildProjectFrameworkUtility.GetProjectFramework(
                    projectFilePath: filePath,
                    targetFrameworkMoniker: targetFrameworkMoniker,
                    targetPlatformMoniker: targetPlatforMoniker,
                    targetPlatformMinVersion: targetPlatformMinVersion,
                    clrSupport: clrSupport,
                    windowsTargetPlatformMinVersion: windowsTargetPlatformMinVersion);

                string runtimeIdentifierGraphPath = null;
                ImmutableArray<NuGetFramework> imports = [];
                bool assetTargetFallback = false;
                bool warn = false;

                if (restoreType == ProjectStyle.PackageReference)
                {
                    var packageTargetFallback = MSBuildStringUtility.Split(item.GetProperty("PackageTargetFallback"))
                        .Select(NuGetFramework.Parse)
                        .ToList();

                    var assetTargetFallbackList = MSBuildStringUtility.Split(item.GetProperty(AssetTargetFallbackUtility.AssetTargetFallback))
                        .Select(NuGetFramework.Parse)
                        .ToList();

                    // Throw if an invalid combination was used.
                    AssetTargetFallbackUtility.EnsureValidFallback(packageTargetFallback, assetTargetFallbackList, filePath);

                    // Update the framework appropriately
                    (targetFramework, imports, assetTargetFallback, warn) = AssetTargetFallbackUtility.GetFallbackFrameworkInformation(targetFramework, packageTargetFallback, assetTargetFallbackList);

                    runtimeIdentifierGraphPath = item.GetProperty("RuntimeIdentifierGraphPath");
                }

                var targetFrameworkInfo = new TargetFrameworkInformation()
                {
                    AssetTargetFallback = assetTargetFallback,
                    FrameworkName = targetFramework,
                    Imports = imports,
                    RuntimeIdentifierGraphPath = runtimeIdentifierGraphPath,
                    TargetAlias = targetAlias,
                    Warn = warn
                };

                yield return targetFrameworkInfo;
            }
        }

        /// <summary>
        /// Remove duplicates and excluded values a set of sources or fallback folders.
        /// </summary>
        /// <remarks>Compares with Ordinal, excludes must be exact matches.</remarks>
        public static IEnumerable<string> AggregateSources(IEnumerable<string> values, IEnumerable<string> excludeValues)
        {
            if (values == null)
            {
                throw new ArgumentNullException(nameof(values));
            }

            if (excludeValues == null)
            {
                throw new ArgumentNullException(nameof(excludeValues));
            }

            var result = new SortedSet<string>(values, StringComparer.Ordinal);

            // Remove excludes
            result.ExceptWith(excludeValues);

            return result;
        }

        private static RuntimeGraph GetRuntimeGraph(IMSBuildItem specItem)
        {
            var runtimes = MSBuildStringUtility.Split(specItem.GetProperty("RuntimeIdentifiers"))
                .Distinct(StringComparer.Ordinal)
                .Select(rid => new RuntimeDescription(rid))
                .ToList();

            var supports = MSBuildStringUtility.Split(specItem.GetProperty("RuntimeSupports"))
                .Distinct(StringComparer.Ordinal)
                .Select(s => new CompatibilityProfile(s))
                .ToList();

            return new RuntimeGraph(runtimes, supports);
        }

        private static void AddProjectReferences(PackageSpec spec, IEnumerable<IMSBuildItem> items)
        {
            // Add groups for each spec framework
            var aliasGroups = new Dictionary<string, List<ProjectRestoreReference>>();

            foreach (string alias in spec.TargetFrameworks.Select(e => e.TargetAlias).Distinct())
            {
                aliasGroups.Add(alias, new List<ProjectRestoreReference>());
            }

            var flatReferences = GetItemByType(items, "ProjectReference")
                .Select(GetProjectRestoreReference);

            var comparer = PathUtility.GetStringComparerBasedOnOS();

            // Add project paths
            foreach (var frameworkPair in flatReferences)
            {
                // If no frameworks were given, apply to all
                var addToFrameworks = frameworkPair.Item1.Count == 0
                    ? aliasGroups.Keys.ToList()
                    : frameworkPair.Item1;

                foreach (var framework in addToFrameworks)
                {
                    List<ProjectRestoreReference> references;
                    if (aliasGroups.TryGetValue(framework, out references))
                    {
                        // Ensure unique
                        if (!references.Any(e => comparer.Equals(e.ProjectUniqueName, frameworkPair.Item2.ProjectUniqueName)))
                        {
                            references.Add(frameworkPair.Item2);
                        }
                    }
                }
            }

            // Add groups to spec
            foreach (KeyValuePair<string, List<ProjectRestoreReference>> frameworkPair in aliasGroups)
            {
                TargetFrameworkInformation targetFrameworkInformation = spec.TargetFrameworks.Single(e => e.TargetAlias.Equals(frameworkPair.Key, StringComparison.Ordinal));
                spec.RestoreMetadata.TargetFrameworks.Add(new ProjectRestoreMetadataFrameworkInfo(targetFrameworkInformation.FrameworkName)
                {
                    ProjectReferences = frameworkPair.Value,
                    TargetAlias = targetFrameworkInformation.TargetAlias
                });
            }
        }

        private static Tuple<List<string>, ProjectRestoreReference> GetProjectRestoreReference(IMSBuildItem item)
        {
            var frameworks = GetFrameworks(item).ToList();

            var reference = new ProjectRestoreReference()
            {
                ProjectPath = item.GetProperty("ProjectPath"),
                ProjectUniqueName = item.GetProperty("ProjectReferenceUniqueName"),
            };

            ApplyIncludeFlags(reference, item.GetProperty("IncludeAssets"), item.GetProperty("ExcludeAssets"), item.GetProperty("PrivateAssets"));

            return new Tuple<List<string>, ProjectRestoreReference>(frameworks, reference);
        }

        private static bool AddDownloadDependencyIfNotExist(PackageSpec spec, string targetAlias, DownloadDependency dependency)
        {
            var index = spec.TargetFrameworks.SingleIndex(e => e.TargetAlias.Equals(targetAlias, StringComparison.Ordinal));
            var frameworkInfo = spec.TargetFrameworks[index];

            if (!frameworkInfo.DownloadDependencies.Contains(dependency))
            {
                var newDownloadDependencies = frameworkInfo.DownloadDependencies.Add(dependency);
                spec.TargetFrameworks[index] = new TargetFrameworkInformation(frameworkInfo) { DownloadDependencies = newDownloadDependencies };

                return true;
            }
            return false;
        }

        private static bool AddDependencyIfNotExist(PackageSpec spec, string targetAlias, LibraryDependency dependency)
        {
            var index = spec.TargetFrameworks.SingleIndex(e => e.TargetAlias.Equals(targetAlias, StringComparison.Ordinal));
            var frameworkInfo = spec.TargetFrameworks[index];
            var dependencies = frameworkInfo.Dependencies;

            if (!dependencies
                            .Select(d => d.Name)
                            .Contains(dependency.Name, StringComparer.OrdinalIgnoreCase))
            {
                spec.TargetFrameworks[index] = new TargetFrameworkInformation(frameworkInfo) { Dependencies = dependencies.Add(dependency) };

                return true;
            }

            return false;
        }

        private static void AddPackageReferences(PackageSpec spec, IEnumerable<IMSBuildItem> items, bool isCpvmEnabled)
        {
            if (isCpvmEnabled)
            {
                AddCentralPackageVersions(spec, items);
            }

            var dict = new Dictionary<string, TargetFrameworkInformation>(StringComparer.OrdinalIgnoreCase);
            foreach (var targetFramework in spec.TargetFrameworks)
            {
                dict.Add(targetFramework.TargetAlias, targetFramework);
            }

            foreach (var item in GetItemByType(items, "Dependency"))
            {
                // Get warning suppressions
                var noWarn = MSBuildStringUtility.GetNuGetLogCodes(item.GetProperty("NoWarn"));
                (var includeType, var suppressParent) = GetLibraryDependencyIncludeFlags(item);
                IEnumerable<string> frameworks = GetFrameworks(item);
                string name = item.GetProperty("Id");
                bool autoReferenced = IsPropertyTrue(item, "IsImplicitlyDefined");
                VersionRange versionOverrideRange = GetVersionRange(item, defaultValue: null, "VersionOverride");

                VersionRange versionRange = GetVersionRange(item, defaultValue: isCpvmEnabled ? null : VersionRange.All);
                bool versionDefined = versionRange != null;
                if (versionRange == null && !isCpvmEnabled)
                {
                    versionRange = VersionRange.All;
                }

                if (!frameworks.Any())
                {
                    frameworks = dict.Keys;
                }

                foreach (var framework in frameworks)
                {
                    dict.TryGetValue(framework, out TargetFrameworkInformation frameworkInfo);
                    CentralPackageVersion centralPackageVersion = null;
                    bool isCentrallyManaged = !versionDefined && !autoReferenced && isCpvmEnabled && versionOverrideRange == null && frameworkInfo.CentralPackageVersions != null && frameworkInfo.CentralPackageVersions.TryGetValue(name, out centralPackageVersion);

                    if (centralPackageVersion != null)
                    {
                        versionRange = centralPackageVersion.VersionRange;
                    }
                    versionRange = versionOverrideRange ?? versionRange;

                    var dependency = new LibraryDependency()
                    {
                        LibraryRange = new LibraryRange(
                            name: name,
                            versionRange: versionRange,
                            typeConstraint: LibraryDependencyTarget.Package),

                        AutoReferenced = autoReferenced,
                        GeneratePathProperty = IsPropertyTrue(item, "GeneratePathProperty"),
                        Aliases = item.GetProperty("Aliases"),
                        VersionOverride = versionOverrideRange,
                        NoWarn = noWarn,
                        IncludeType = includeType,
                        SuppressParent = suppressParent,
                        VersionCentrallyManaged = isCentrallyManaged,
                    };

                    AddDependencyIfNotExist(spec, framework, dependency);
                }
            }
        }

        internal static void AddPrunePackageReferences(PackageSpec spec, IEnumerable<IMSBuildItem> items)
        {
            var prunePackageReferences = new Dictionary<string, Dictionary<string, PrunePackageReference>>(StringComparer.OrdinalIgnoreCase);
            var isPruningEnabled = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
            foreach (var targetFramework in spec.TargetFrameworks)
            {
                prunePackageReferences.Add(targetFramework.TargetAlias, new Dictionary<string, PrunePackageReference>(StringComparer.OrdinalIgnoreCase));
            }

            List<IMSBuildItem> targetFrameworkInfos = GetItemByType(items, "TargetFrameworkInformation").ToList();
            bool isPruningEnabledGlobally = false;
            foreach (var item in targetFrameworkInfos)
            {
                if (IsPropertyTrue(item, "RestorePackagePruningDefault"))
                {
                    isPruningEnabledGlobally = true;
                    break;
                }
            }

            foreach (var item in targetFrameworkInfos)
            {
                var tfm = item.GetProperty("TargetFramework") ?? string.Empty;

                bool? restoreEnablePackagePruning = MSBuildStringUtility.GetBooleanOrNull(item.GetProperty("RestoreEnablePackagePruning"));
                bool isPackagePruningEnabled = restoreEnablePackagePruning == null ? isPruningEnabledGlobally : restoreEnablePackagePruning == true;

                isPruningEnabled[tfm] = isPackagePruningEnabled;
            }

            foreach (var item in GetItemByType(items, "PrunePackageReference"))
            {
                var id = item.GetProperty("Id");
                var versionString = item.GetProperty("VersionRange");
                HashSet<string> tfms = GetFrameworks(item);

                if (tfms.Count > 0)
                {
                    foreach (var targetAlias in tfms)
                    {
                        if (isPruningEnabled[targetAlias])
                        {
                            var frameworkInfo = prunePackageReferences[targetAlias];
                            AddPackageToPrune(id, versionString, frameworkInfo);
                        }
                    }
                }
                else
                {
                    foreach (var frameworkInfo in prunePackageReferences)
                    {
                        if (isPruningEnabled[frameworkInfo.Key])
                        {
                            AddPackageToPrune(id, versionString, frameworkInfo.Value);
                        }
                    }
                }
            }

            for (int i = 0; i < spec.TargetFrameworks.Count; i++)
            {
                spec.TargetFrameworks[i] = new TargetFrameworkInformation(spec.TargetFrameworks[i]) { PackagesToPrune = prunePackageReferences[spec.TargetFrameworks[i].TargetAlias] };
            }

            static void AddPackageToPrune(string id, string version, Dictionary<string, PrunePackageReference> frameworkInfo)
            {
                if (!frameworkInfo.ContainsKey(id))
                {
                    frameworkInfo.Add(id, PrunePackageReference.Create(id, version!));
                }
            }
        }

        internal static void AddPackageDownloads(PackageSpec spec, IEnumerable<IMSBuildItem> items)
        {
            var splitChars = new[] { ';' };

            foreach (var item in GetItemByType(items, "DownloadDependency"))
            {
                var id = item.GetProperty("Id");
                var versionString = item.GetProperty("VersionRange");
                if (string.IsNullOrEmpty(versionString))
                {
                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.Error_PackageDownload_NoVersion, id));
                }

                var versions = versionString.Split(splitChars, StringSplitOptions.RemoveEmptyEntries);

                foreach (var version in versions)
                {
                    var versionRange = GetVersionRange(version, defaultValue: VersionRange.All);

                    if (!(versionRange.HasLowerAndUpperBounds && versionRange.MinVersion.Equals(versionRange.MaxVersion)))
                    {
                        throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.Error_PackageDownload_OnlyExactVersionsAreAllowed, id, versionRange.OriginalString));
                    }

                    var downloadDependency = new DownloadDependency(id, versionRange);

                    var frameworks = GetFrameworks(item);
                    foreach (var framework in frameworks)
                    {
                        AddDownloadDependencyIfNotExist(spec, framework, downloadDependency);
                    }
                }
            }
        }

        private static (LibraryIncludeFlags includeType, LibraryIncludeFlags suppressParent) GetLibraryDependencyIncludeFlags(IMSBuildItem item)
        {
            return GetLibraryDependencyIncludeFlags(item.GetProperty("IncludeAssets"), item.GetProperty("ExcludeAssets"), item.GetProperty("PrivateAssets"));
        }

        private static LibraryIncludeFlags GetIncludeFlags(string value, LibraryIncludeFlags defaultValue)
        {
            var parts = MSBuildStringUtility.Split(value);

            if (parts.Length > 0)
            {
                return LibraryIncludeFlagUtils.GetFlags(parts);
            }
            else
            {
                return defaultValue;
            }
        }

        private static void AddFrameworkReferences(PackageSpec spec, IEnumerable<IMSBuildItem> items)
        {
            foreach (var item in GetItemByType(items, "FrameworkReference"))
            {
                var frameworkReference = item.GetProperty("Id");
                var frameworks = GetFrameworks(item);

                var privateAssets = item.GetProperty("PrivateAssets");

                foreach (var framework in frameworks)
                {
                    AddFrameworkReferenceIfNotExists(spec, framework, frameworkReference, privateAssets);
                }
            }
        }

        private static bool AddFrameworkReferenceIfNotExists(PackageSpec spec, string targetAlias, string frameworkReference, string privateAssetsValue)
        {
            var index = spec.TargetFrameworks.SingleIndex(e => e.TargetAlias.Equals(targetAlias, StringComparison.Ordinal));
            TargetFrameworkInformation frameworkInfo = spec.TargetFrameworks[index];

            if (!frameworkInfo
                .FrameworkReferences
                .Select(f => f.Name)
                .Contains(frameworkReference, ComparisonUtility.FrameworkReferenceNameComparer))
            {
                var privateAssets = FrameworkDependencyFlagsUtils.GetFlags(MSBuildStringUtility.Split(privateAssetsValue));
                FrameworkDependency[] newFrameworkReferences = [.. frameworkInfo.FrameworkReferences, new FrameworkDependency(frameworkReference, privateAssets)];
                spec.TargetFrameworks[index] = new TargetFrameworkInformation(frameworkInfo) { FrameworkReferences = newFrameworkReferences };
                return true;
            }
            return false;
        }

        private static VersionRange GetVersionRange(IMSBuildItem item, VersionRange defaultValue, string propertyName = "VersionRange")
        {
            var rangeString = item.GetProperty(propertyName);
            return GetVersionRange(rangeString, defaultValue);
        }

        private static VersionRange GetVersionRange(string rangeString, VersionRange defaultValue)
        {
            if (!string.IsNullOrEmpty(rangeString))
            {
                return VersionRange.Parse(rangeString);
            }

            return defaultValue;
        }

        private static PackageSpec GetBaseSpec(IMSBuildItem specItem, ProjectStyle projectStyle, IEnumerable<IMSBuildItem> items)
        {
            var spec = new PackageSpec();
            spec.RestoreMetadata = projectStyle == ProjectStyle.PackagesConfig
                ? new PackagesConfigProjectRestoreMetadata()
                : new ProjectRestoreMetadata();
            spec.FilePath = specItem.GetProperty("ProjectPath");
            spec.RestoreMetadata.ProjectName = specItem.GetProperty("ProjectName");

            if (projectStyle == ProjectStyle.DotnetCliTool || projectStyle == ProjectStyle.Unknown || projectStyle == ProjectStyle.PackagesConfig)
            {
                var tfmProperty = specItem.GetProperty("TargetFrameworks");
                if (!string.IsNullOrEmpty(tfmProperty))
                {
                    var needsAlias = projectStyle == ProjectStyle.DotnetCliTool;
                    spec.TargetFrameworks.Add(
                        new TargetFrameworkInformation()
                        {
                            FrameworkName = NuGetFramework.Parse(tfmProperty),
                            TargetAlias = needsAlias ? tfmProperty : string.Empty
                        });
                }
            }
            else
            {
                spec.TargetFrameworks.AddRange(GetTargetFrameworkInformation(spec.FilePath, projectStyle, items).Distinct());
            }
            return spec;
        }

        private static HashSet<string> GetFrameworks(IMSBuildItem item)
        {
            return GetTargetFrameworkStrings(item);
        }

        private static HashSet<string> GetTargetFrameworkStrings(IMSBuildItem item)
        {
            var frameworks = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            var frameworksString = item.GetProperty("TargetFrameworks");
            if (!string.IsNullOrEmpty(frameworksString))
            {
                frameworks.UnionWith(MSBuildStringUtility.Split(frameworksString));
            }

            return frameworks;
        }

        private static IEnumerable<IMSBuildItem> GetItemByType(IEnumerable<IMSBuildItem> items, string type)
        {
            return items.Where(e => e.IsType(type));
        }

        private static bool IsType(this IMSBuildItem item, string type)
        {
            return StringComparer.OrdinalIgnoreCase.Equals(type, item?.GetProperty("Type"));
        }

        /// <summary>
        /// Return the parsed version or 1.0.0 if the property does not exist.
        /// </summary>
        private static NuGetVersion GetVersion(IMSBuildItem item)
        {
            var versionString = item.GetProperty("Version");
            NuGetVersion version = null;

            if (string.IsNullOrEmpty(versionString))
            {
                // Default to 1.0.0 if the property does not exist
                version = new NuGetVersion(1, 0, 0);
            }
            else
            {
                // Snapshot versions are not allowed in .NETCore
                version = NuGetVersion.Parse(versionString);
            }

            return version;
        }

        public static void Dump(IEnumerable<IMSBuildItem> items, ILogger log)
        {
            foreach (var item in items)
            {
                log.LogDebug($"Item: {item.Identity}");

                foreach (var key in item.Properties)
                {
                    var val = item.GetProperty(key);

                    if (!string.IsNullOrEmpty(val))
                    {
                        log.LogDebug($"  {key}={val}");
                    }
                }
            }
        }

        private static WarningProperties GetWarningProperties(IMSBuildItem specItem)
        {
            return WarningProperties.GetWarningProperties(
                treatWarningsAsErrors: specItem.GetProperty("TreatWarningsAsErrors"),
                warningsAsErrors: specItem.GetProperty("WarningsAsErrors"),
                noWarn: specItem.GetProperty("NoWarn"),
                warningsNotAsErrors: specItem.GetProperty("WarningsNotAsErrors"));
        }

        private static RestoreLockProperties GetRestoreLockProperties(IMSBuildItem specItem)
        {
            return new RestoreLockProperties(
                specItem.GetProperty("RestorePackagesWithLockFile"),
                specItem.GetProperty("NuGetLockFilePath"),
                IsPropertyTrue(specItem, "RestoreLockedMode"));
        }

        public static RestoreAuditProperties GetRestoreAuditProperties(IMSBuildItem specItem, IEnumerable<IMSBuildItem> allItems, HashSet<string> suppressionItems)
        {
            string enableAudit = specItem.GetProperty("NuGetAudit");
            string auditLevel = specItem.GetProperty("NuGetAuditLevel");
            string auditMode = GetAuditMode(specItem, allItems);

            if (enableAudit != null || auditLevel != null || auditMode != null
                || (suppressionItems != null && suppressionItems.Count > 0))
            {
                return new RestoreAuditProperties()
                {
                    EnableAudit = enableAudit,
                    AuditLevel = auditLevel,
                    AuditMode = auditMode,
                    SuppressedAdvisories = suppressionItems?.Count > 0 ? suppressionItems : null
                };
            }

            return null;

            // We want to set NuGetAuditMode to "all" if a multi-targeting project targets .NET 10 or higher.
            // However, that can only be done by an "inner build" evaulation, but we read other audit settings
            // from the project evaluation, not inner-builds. So, check the inner builds if any TFM sets mode
            // to "all", otherwise use the project's "outer build" mode.
            string GetAuditMode(IMSBuildItem project, IEnumerable<IMSBuildItem> tfms)
            {
                foreach (var item in tfms.NoAllocEnumerate())
                {
                    string auditMode = item.GetProperty("NuGetAuditMode");
                    if (string.Equals(auditMode, "all", StringComparison.OrdinalIgnoreCase))
                    {
                        return auditMode;
                    }
                }

                string projectAuditMode = project.GetProperty("NuGetAuditMode");
                return projectAuditMode;
            }
        }

        public static NuGetVersion GetSdkAnalysisLevel(string sdkAnalysisLevel)
        {
            if (string.IsNullOrEmpty(sdkAnalysisLevel))
            {
                return null;
            }

            if (!NuGetVersion.TryParse(sdkAnalysisLevel, out NuGetVersion version))
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.Invalid_AttributeValue, "SdkAnalysisLevel", sdkAnalysisLevel, "9.0.100"));
            }

            return version;
        }

        public static bool GetUsingMicrosoftNETSdk(string usingMicrosoftNETSdk)
        {
            if (string.IsNullOrEmpty(usingMicrosoftNETSdk))
            {
                return false;
            }

            if (!bool.TryParse(usingMicrosoftNETSdk, out var result))
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.Invalid_AttributeValue, "UsingMicrosoftNETSdk", usingMicrosoftNETSdk, "false"));
            }

            return result;
        }

        private static HashSet<string> GetAuditSuppressions(IEnumerable<IMSBuildItem> items)
        {
            IEnumerable<string> suppressions = GetItemByType(items, "NuGetAuditSuppress")
                                                    .Select(i => i.GetProperty("Id"));

            return (suppressions != null && suppressions.Any())
                    ? new HashSet<string>(suppressions)
                    : null;
        }

        /// <summary>
        /// Convert http:/url to http://url
        /// If not needed the same path is returned. This is to work around
        /// issues with msbuild dropping slashes from paths on linux and osx.
        /// </summary>
        public static string FixSourcePath(string s)
        {
            var result = s;

            if (result.IndexOf("/", StringComparison.Ordinal) >= -1 && result.IndexOf(DoubleSlash, StringComparison.Ordinal) == -1)
            {
                for (var i = 0; i < HttpPrefixes.Length; i++)
                {
                    result = FixSourcePath(result, HttpPrefixes[i], DoubleSlash);
                }

                // For non-windows machines use file:///
                var fileSlashes = RuntimeEnvironmentHelper.IsWindows ? DoubleSlash : "///";
                result = FixSourcePath(result, "file:", fileSlashes);
            }

            return result;
        }

        private static string FixSourcePath(string s, string prefixWithoutSlashes, string slashes)
        {
            if (s.Length >= (prefixWithoutSlashes.Length + 2)
                && s.StartsWith($"{prefixWithoutSlashes}/", StringComparison.OrdinalIgnoreCase)
                && !s.StartsWith($"{prefixWithoutSlashes}{DoubleSlash}", StringComparison.OrdinalIgnoreCase))
            {
                // original prefix casing + // + rest of the path
                return s.Substring(0, prefixWithoutSlashes.Length) + slashes + s.Substring(prefixWithoutSlashes.Length + 1);
            }

            return s;
        }

        internal static bool IsPropertyFalse(IMSBuildItem item, string propertyName, bool defaultValue = false)
        {
            string value = item.GetProperty(propertyName);

            if (string.IsNullOrWhiteSpace(value))
            {
                return defaultValue;
            }

            return string.Equals(value, bool.FalseString, StringComparison.OrdinalIgnoreCase);
        }

        internal static bool IsPropertyTrue(IMSBuildItem item, string propertyName, bool defaultValue = false)
        {
            string value = item.GetProperty(propertyName);

            if (string.IsNullOrWhiteSpace(value))
            {
                return defaultValue;
            }

            return string.Equals(value, bool.TrueString, StringComparison.OrdinalIgnoreCase);
        }

        /// <summary>
        /// Function used to display errors and warnings at the end of restore operation.
        /// The errors and warnings are read from the assets file based on restore result.
        /// </summary>
        /// <param name="messages">Messages to log.</param>
        /// <param name="logger">Logger used to display warnings and errors.</param>
        public static Task ReplayWarningsAndErrorsAsync(IEnumerable<IAssetsLogMessage> messages, ILogger logger)
        {
            if (logger == null)
            {
                throw new ArgumentNullException(nameof(logger));
            }

            var logMessages = messages?.Select(m => m.AsRestoreLogMessage()) ??
                              Enumerable.Empty<RestoreLogMessage>();

            return logger.LogMessagesAsync(logMessages);
        }

        private static Dictionary<string, Dictionary<string, CentralPackageVersion>> CreateCentralVersionDependencies(IEnumerable<IMSBuildItem> items,
            IList<TargetFrameworkInformation> specFrameworks)
        {
            IEnumerable<IMSBuildItem> centralVersions = GetItemByType(items, "CentralPackageVersion")?.Distinct(MSBuildItemIdentityComparer.Default).ToList();
            var result = new Dictionary<string, Dictionary<string, CentralPackageVersion>>();

            foreach (IMSBuildItem cv in centralVersions)
            {
                HashSet<string> tfms = GetFrameworks(cv);
                string version = cv.GetProperty("VersionRange");
                CentralPackageVersion centralPackageVersion = new CentralPackageVersion(cv.GetProperty("Id"), string.IsNullOrWhiteSpace(version) ? VersionRange.All : VersionRange.Parse(version));

                if (tfms.Count > 0)
                {
                    AddCentralPackageVersion(result, centralPackageVersion, tfms);
                }
                else
                {
                    AddCentralPackageVersion(result, centralPackageVersion, specFrameworks.Select(f => f.TargetAlias));
                }
            }

            return result;
        }

        private static void AddCentralPackageVersion(Dictionary<string, Dictionary<string, CentralPackageVersion>> centralPackageVersions,
            CentralPackageVersion centralPackageVersion,
            IEnumerable<string> frameworks)
        {
            foreach (var framework in frameworks)
            {
                if (!centralPackageVersions.TryGetValue(framework, out Dictionary<string, CentralPackageVersion> versions))
                {
                    versions = new Dictionary<string, CentralPackageVersion>(StringComparer.OrdinalIgnoreCase);

                    centralPackageVersions.Add(framework, versions);
                }

                versions[centralPackageVersion.Name] = centralPackageVersion;
            }
        }

        private static ProjectStyle GetProjectStyle(IMSBuildItem projectSpecItem)
        {
            var typeString = projectSpecItem.GetProperty("ProjectStyle");
            var restoreType = ProjectStyle.Unknown;

            if (!string.IsNullOrEmpty(typeString))
            {
                Enum.TryParse(typeString, ignoreCase: true, result: out restoreType);
            }

            return restoreType;
        }

        /// <summary>
        /// Determines the current settings for central package management for the specified project.
        /// </summary>
        /// <param name="projectSpecItem">The <see cref="IMSBuildItem" /> to get the central package management settings from.</param>
        /// <param name="projectStyle">The <see cref="ProjectStyle?" /> of the specified project.  Specify <see langword="null" /> when the project does not define a restore style.</param>
        /// <returns>A <see cref="Tuple{T1, T2, T3, T4}" /> containing values indicating whether or not central package management is enabled, if the ability to override a package version </returns>
        public static (bool IsEnabled, bool IsVersionOverrideDisabled, bool IsCentralPackageTransitivePinningEnabled, bool isCentralPackageFloatingVersionsEnabled) GetCentralPackageManagementSettings(IMSBuildItem projectSpecItem, ProjectStyle projectStyle)
        {
            if (projectStyle == ProjectStyle.PackageReference)
            {
                bool isEnabled = IsPropertyTrue(projectSpecItem, "_CentralPackageVersionsEnabled");
                bool isVersionOverrideDisabled = IsPropertyFalse(projectSpecItem, "CentralPackageVersionOverrideEnabled");
                bool isCentralPackageTransitivePinningEnabled = IsPropertyTrue(projectSpecItem, "CentralPackageTransitivePinningEnabled");
                bool isCentralPackageFloatingVersionsEnabled = IsPropertyTrue(projectSpecItem, "CentralPackageFloatingVersionsEnabled");
                return (isEnabled, isVersionOverrideDisabled, isCentralPackageTransitivePinningEnabled, isCentralPackageFloatingVersionsEnabled);
            }

            return (false, false, false, false);
        }

        private static void AddCentralPackageVersions(PackageSpec spec, IEnumerable<IMSBuildItem> items)
        {
            var centralVersionsDependencies = CreateCentralVersionDependencies(items, spec.TargetFrameworks);
            foreach ((var targetAlias, var versions) in centralVersionsDependencies)
            {
                var index = spec.TargetFrameworks.FirstIndex(f => targetAlias.Equals(f.TargetAlias, StringComparison.OrdinalIgnoreCase));
                var frameworkInfo = spec.TargetFrameworks[index];

                spec.TargetFrameworks[index] = new TargetFrameworkInformation(frameworkInfo)
                {
                    CentralPackageVersions = versions,
                };
            }
        }
    }
}