File: MSBuildStaticGraphRestore.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Build.Tasks.Console\NuGet.Build.Tasks.Console.csproj (NuGet.Build.Tasks.Console)
// 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.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Build.Construction;
using Microsoft.Build.Definition;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Evaluation.Context;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Graph;
using Microsoft.Build.Logging;
using NuGet.Commands;
using NuGet.Commands.Restore;
using NuGet.Commands.Restore.Utility;
using NuGet.Common;
using NuGet.Configuration;
using NuGet.Frameworks;
using NuGet.LibraryModel;
using NuGet.Packaging;
using NuGet.ProjectModel;
using NuGet.RuntimeModel;
using NuGet.Versioning;

namespace NuGet.Build.Tasks.Console
{
    internal sealed class MSBuildStaticGraphRestore : IDisposable
    {
        /// <summary>
        /// Represents the name of the environment variable that user can set to specify MSBuild binary logger parameters.
        /// </summary>
        public const string BinaryLoggerParameterEnvironmentVariable = "RESTORE_TASK_BINLOG_PARAMETERS";

        private static readonly Lazy<IMachineWideSettings> MachineWideSettingsLazy = new Lazy<IMachineWideSettings>(() => new XPlatMachineWideSetting());

        /// <summary>
        /// Represents the small list of targets that must be executed in order for various restore input items to be accurate.
        /// </summary>
        private static readonly string[] TargetsToBuild =
        {
            "_CollectRestoreInputs"
        };

        private readonly IEnvironmentVariableReader _environment;

        private readonly Lazy<ConsoleLoggingQueue> _loggingQueueLazy;

        private readonly Lazy<MSBuildLogger> _msBuildLoggerLazy;

        private readonly SettingsLoadingContext _settingsLoadContext = new SettingsLoadingContext();

        public MSBuildStaticGraphRestore(IEnvironmentVariableReader environment = null)
        {
            _environment = environment ?? EnvironmentVariableWrapper.Instance;
            _loggingQueueLazy = new Lazy<ConsoleLoggingQueue>(() => new ConsoleLoggingQueue(LoggerVerbosity.Normal));
            _msBuildLoggerLazy = new Lazy<MSBuildLogger>(() => new MSBuildLogger(LoggingQueue.TaskLoggingHelper));
        }

        /// <summary>
        /// Gets a <see cref="ConsoleLoggingQueue" /> object to be used for logging.
        /// </summary>
        private ConsoleLoggingQueue LoggingQueue => _loggingQueueLazy.Value;

        /// <summary>
        /// Gets a <see cref="MSBuildLogger" /> object to be used for logging.
        /// </summary>
        private MSBuildLogger MSBuildLogger => _msBuildLoggerLazy.Value;

        public void Dispose()
        {
            if (_loggingQueueLazy.IsValueCreated)
            {
                // Disposing the logging queue will wait for the queue to be drained
                _loggingQueueLazy.Value.Dispose();
            }

            _settingsLoadContext.Dispose();
        }

        /// <summary>
        /// Restores the specified projects.
        /// </summary>
        /// <param name="entryProjectFilePath">The main project to restore.  This can be a project for a Visual Studio© Solution File.</param>
        /// <param name="globalProperties">The global properties to use when evaluation MSBuild projects.</param>
        /// <param name="options">The set of options to use when restoring.  These options come from the main MSBuild process and control how restore functions.</param>
        /// <returns><code>true</code> if the restore succeeded, otherwise <code>false</code>.</returns>
        [MethodImpl(MethodImplOptions.NoInlining)]
        public async Task<bool> RestoreAsync(string entryProjectFilePath, IDictionary<string, string> globalProperties, IReadOnlyDictionary<string, string> options)
        {
            bool interactive = IsOptionTrue(nameof(RestoreTaskEx.Interactive), options);

            string binaryLoggerParameters = GetBinaryLoggerParameters(_environment, options);

            var dependencyGraphSpec = GetDependencyGraphSpec(entryProjectFilePath, globalProperties, interactive, binaryLoggerParameters, EnvironmentVariableWrapper.Instance);

            // If the dependency graph spec is null, something went wrong evaluating the projects, so return false
            if (dependencyGraphSpec == null)
            {
                return false;
            }

            static bool HasProjectToRestore(DependencyGraphSpec dgSpec, bool restorePackagesConfig)
            {
                if (dgSpec.Restore.Count > 0)
                {
                    return true;
                }

#if NETFRAMEWORK
                if (restorePackagesConfig)
                {
                    for (int i = 0; i < dgSpec.Projects.Count; i++)
                    {
                        PackageSpec project = dgSpec.Projects[i];
                        if (project.RestoreMetadata?.ProjectStyle == ProjectStyle.PackagesConfig)
                        {
                            return true;
                        }
                    }
                }
#endif

                return false;
            }

            bool restorePackagesConfig = IsOptionTrue(nameof(RestoreTaskEx.RestorePackagesConfig), options);
            if ((string.Equals(Path.GetExtension(entryProjectFilePath), ".sln", StringComparison.OrdinalIgnoreCase)
                || string.Equals(Path.GetExtension(entryProjectFilePath), ".slnx", StringComparison.OrdinalIgnoreCase))
                    && !HasProjectToRestore(dependencyGraphSpec, restorePackagesConfig))
            {
                MSBuildLogger.LogInformation(string.Format(CultureInfo.CurrentCulture, Strings.Log_NoProjectsForRestore));
                return true;
            }

            try
            {
                // todo: need to return Restore task output properties, like in NuGet.targets
                // https://github.com/NuGet/Home/issues/13828
                List<RestoreSummary> restoreSummaries = await BuildTasksUtility.RestoreAsync(
                    dependencyGraphSpec: dependencyGraphSpec,
                    interactive,
                    recursive: IsOptionTrue(nameof(RestoreTaskEx.Recursive), options),
                    noCache: IsOptionTrue(nameof(RestoreTaskEx.NoCache), options) || IsOptionTrue(nameof(RestoreTaskEx.NoHttpCache), options),
                    ignoreFailedSources: IsOptionTrue(nameof(RestoreTaskEx.IgnoreFailedSources), options),
                    disableParallel: IsOptionTrue(nameof(RestoreTaskEx.DisableParallel), options),
                    force: IsOptionTrue(nameof(RestoreTaskEx.Force), options),
                    forceEvaluate: IsOptionTrue(nameof(RestoreTaskEx.ForceEvaluate), options),
                    hideWarningsAndErrors: IsOptionTrue(nameof(RestoreTaskEx.HideWarningsAndErrors), options),
                    restorePC: restorePackagesConfig,
                    cleanupAssetsForUnsupportedProjects: IsOptionTrue(nameof(RestoreTaskEx.CleanupAssetsForUnsupportedProjects), options),
                    log: MSBuildLogger,
                cancellationToken: CancellationToken.None);
                bool result = restoreSummaries.All(rs => rs.Success);

                LogFilesToEmbedInBinlog(dependencyGraphSpec, options);

                return result;
            }
            catch (Exception e)
            {
                LogErrorFromException(e);

                return false;
            }
        }

        /// <summary>
        /// Generates a dependency graph spec for the given properties.
        /// </summary>
        /// <param name="entryProjectFilePath">The main project to generate that graph for.  This can be a project for a Visual Studio© Solution File.</param>
        /// <param name="globalProperties">The global properties to use when evaluation MSBuild projects.</param>
        /// <param name="options">The set of options to use to generate the graph, including the restore graph output path.</param>
        /// <returns><code>true</code> if the dependency graph spec was generated and written, otherwise <code>false</code>.</returns>
        public bool WriteDependencyGraphSpec(string entryProjectFilePath, IDictionary<string, string> globalProperties, IReadOnlyDictionary<string, string> options)
        {
            bool interactive = IsOptionTrue(nameof(RestoreTaskEx.Interactive), options);

            string binaryLoggerParameters = GetBinaryLoggerParameters(_environment, options);

            var dependencyGraphSpec = GetDependencyGraphSpec(entryProjectFilePath, globalProperties, interactive, binaryLoggerParameters, EnvironmentVariableWrapper.Instance);

            try
            {
                if (dependencyGraphSpec == null)
                {
                    LoggingQueue.TaskLoggingHelper.LogError(Strings.Error_DgSpecGenerationFailed);
                    return false;
                }

                if (options.TryGetValue("RestoreGraphOutputPath", out var path))
                {
                    dependencyGraphSpec.Save(path);
                    return true;
                }
                else
                {
                    LoggingQueue.TaskLoggingHelper.LogError(Strings.Error_MissingRestoreGraphOutputPath);
                }
            }
            catch (Exception e)
            {
                LogErrorFromException(e);
            }
            return false;
        }

        /// <summary>
        /// Gets parameters for the MSBuild binary logger.
        /// </summary>
        /// <param name="environment">An <see cref="IEnvironmentVariableReader" /> to use when reading environment variables.</param>
        /// <param name="options">The <see cref="IReadOnlyCollection{TKey, TValue}" /> containing user supplied options.</param>
        /// <returns>A <see cref="string" /> containing the parameters for the MSBuild binary logger if specified, otherwise <see langword="null" />.</returns>
        internal static string GetBinaryLoggerParameters(IEnvironmentVariableReader environment, IReadOnlyDictionary<string, string> options)
        {
            string binaryLoggerParameters = environment.GetEnvironmentVariable(BinaryLoggerParameterEnvironmentVariable);

            if (!string.IsNullOrEmpty(binaryLoggerParameters))
            {
                return binaryLoggerParameters;
            }

            // Return null if the binary logger is not enabled
            if (!IsOptionTrue(nameof(RestoreTaskEx.EnableBinaryLogger), options))
            {
                return null;
            }

            if (options.TryGetValue(nameof(RestoreTaskEx.BinaryLoggerParameters), out binaryLoggerParameters) && !string.IsNullOrWhiteSpace(binaryLoggerParameters))
            {
                // User supplied the parameters
                return binaryLoggerParameters;
            }

            // Default parameters
            return binaryLoggerParameters = "LogFile=nuget.binlog";
        }

        /// <summary>
        /// Gets the framework references per target framework for the specified project.
        /// </summary>
        /// <param name="project">The <see cref="ProjectInstance" /> to get framework references for.</param>
        /// <returns>A <see cref="List{FrameworkDependency}" /> containing the framework references for the specified project.</returns>
        internal static IReadOnlyCollection<FrameworkDependency> GetFrameworkReferences(IMSBuildProject project)
        {
            // Get the unique FrameworkReference items, ignoring duplicates
            List<IMSBuildItem> frameworkReferenceItems = GetDistinctItemsOrEmpty(project, "FrameworkReference").ToList();

            if (frameworkReferenceItems.Count == 0)
            {
                return null;
            }

            // For best performance, its better to create a list with the exact number of items needed rather than using a LINQ statement or AddRange.  This is because if the list
            // is not allocated with enough items, the list has to be grown which can slow things down
            var frameworkDependencies = new FrameworkDependency[frameworkReferenceItems.Count];

            for (int i = 0; i < frameworkReferenceItems.Count; i++)
            {
                var frameworkReferenceItem = frameworkReferenceItems[i];
                var privateAssets = MSBuildStringUtility.Split(frameworkReferenceItem.GetProperty("PrivateAssets"));

                frameworkDependencies[i] = new FrameworkDependency(frameworkReferenceItem.Identity, FrameworkDependencyFlagsUtils.GetFlags(privateAssets));
            }

            return frameworkDependencies;
        }

        /// <summary>
        /// Gets the package downloads for the specified project.
        /// </summary>
        /// <param name="project">The <see cref="ProjectInstance" /> to get package downloads for.</param>
        /// <returns>An <see cref="IEnumerable{DownloadDependency}" /> containing the package downloads for the specified project.</returns>
        internal static IEnumerable<DownloadDependency> GetPackageDownloads(IMSBuildProject project)
        {
            // Get the distinct PackageDownload items, ignoring duplicates
            foreach (IMSBuildItem projectItemInstance in GetDistinctItemsOrEmpty(project, "PackageDownload"))
            {
                string id = projectItemInstance.Identity;

                // PackageDownload items can contain multiple versions
                string versionRanges = projectItemInstance.GetProperty("Version");
                if (string.IsNullOrEmpty(versionRanges))
                {
                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.Error_PackageDownload_NoVersion, id));
                }

                foreach (var version in MSBuildStringUtility.Split(versionRanges))
                {
                    // Validate the version range
                    VersionRange versionRange = !string.IsNullOrWhiteSpace(version) ? VersionRange.Parse(version) : VersionRange.All;

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

                    yield return new DownloadDependency(id, versionRange);
                }
            }
        }

        /// <summary>
        /// Gets the centrally defined package version information.
        /// </summary>
        /// <param name="project">The <see cref="ProjectInstance" /> to get PackageVersion for.</param>
        /// <returns>An <see cref="IEnumerable{CentralPackageVersion}" /> containing the package versions for the specified project.</returns>
        internal static Dictionary<string, CentralPackageVersion> GetCentralPackageVersions(IMSBuildProject project)
        {
            var result = new Dictionary<string, CentralPackageVersion>(StringComparer.OrdinalIgnoreCase);
            IEnumerable<IMSBuildItem> packageVersionItems = GetDistinctItemsOrEmpty(project, "PackageVersion");

            foreach (var projectItemInstance in packageVersionItems)
            {
                string id = projectItemInstance.Identity;
                string version = projectItemInstance.GetProperty("Version");
                VersionRange versionRange = string.IsNullOrWhiteSpace(version) ? VersionRange.All : VersionRange.Parse(version);

                result.Add(id, new CentralPackageVersion(id, versionRange));
            }

            return result;
        }

        /// <summary>
        /// Gets the package references for the specified project.
        /// </summary>
        /// <param name="project">The <see cref="ProjectInstance" /> to get package references for.</param>
        /// <param name="isCentralPackageVersionManagementEnabled">A flag for central package version management being enabled.</param>
        /// <returns>A <see cref="List{LibraryDependency}" /> containing the package references for the specified project.</returns>
        internal static ImmutableArray<LibraryDependency> GetPackageReferences(IMSBuildProject project, bool isCentralPackageVersionManagementEnabled, IReadOnlyDictionary<string, CentralPackageVersion> centralPackageVersions)
        {
            // Get the distinct PackageReference items, ignoring duplicates
            List<IMSBuildItem> packageReferenceItems = GetDistinctItemsOrEmpty(project, "PackageReference").ToList();

            var libraryDependencies = new LibraryDependency[packageReferenceItems.Count];

            for (int i = 0; i < packageReferenceItems.Count; i++)
            {
                var packageReferenceItem = packageReferenceItems[i];
                bool autoReferenced = packageReferenceItem.IsPropertyTrue("IsImplicitlyDefined");
                string version = packageReferenceItem.GetProperty("Version");

                VersionRange versionRange = string.IsNullOrWhiteSpace(version) ? null : VersionRange.Parse(version);
                bool versionDefined = versionRange != null;
                if (versionRange == null && !isCentralPackageVersionManagementEnabled)
                {
                    versionRange = VersionRange.All;
                }

                string versionOverrideString = packageReferenceItem.GetProperty("VersionOverride");
                var versionOverrideRange = string.IsNullOrWhiteSpace(versionOverrideString) ? null : VersionRange.Parse(versionOverrideString);

                CentralPackageVersion centralPackageVersion = null;
                bool isCentrallyManaged = !versionDefined && !autoReferenced && isCentralPackageVersionManagementEnabled && versionOverrideRange == null && centralPackageVersions != null && centralPackageVersions.TryGetValue(packageReferenceItem.Identity, out centralPackageVersion);
                if (isCentrallyManaged)
                {
                    versionRange = centralPackageVersion.VersionRange;
                }
                versionRange = versionOverrideRange ?? versionRange;

                ImmutableArray<NuGetLogCode> noWarn = MSBuildStringUtility.GetNuGetLogCodes(packageReferenceItem.GetProperty("NoWarn"));

                libraryDependencies[i] = new LibraryDependency()
                {
                    AutoReferenced = autoReferenced,
                    GeneratePathProperty = packageReferenceItem.IsPropertyTrue("GeneratePathProperty"),
                    Aliases = packageReferenceItem.GetProperty("Aliases"),
                    IncludeType = GetLibraryIncludeFlags(packageReferenceItem.GetProperty("IncludeAssets"), LibraryIncludeFlags.All) & ~GetLibraryIncludeFlags(packageReferenceItem.GetProperty("ExcludeAssets"), LibraryIncludeFlags.None),
                    LibraryRange = new LibraryRange(
                        packageReferenceItem.Identity,
                        versionRange,
                        LibraryDependencyTarget.Package),
                    SuppressParent = GetLibraryIncludeFlags(packageReferenceItem.GetProperty("PrivateAssets"), LibraryIncludeFlagUtils.DefaultSuppressParent),
                    VersionOverride = versionOverrideRange,
                    NoWarn = noWarn,
                    VersionCentrallyManaged = isCentrallyManaged,
                };
            }

            return ImmutableCollectionsMarshal.AsImmutableArray(libraryDependencies);
        }

        internal static Dictionary<string, PrunePackageReference> GetPrunePackageReferences(IMSBuildProject project)
        {
            var result = new Dictionary<string, PrunePackageReference>(StringComparer.OrdinalIgnoreCase);
            IEnumerable<IMSBuildItem> PrunePackageReferences = GetDistinctItemsOrEmpty(project, "PrunePackageReference");

            foreach (var projectItemInstance in PrunePackageReferences)
            {
                string id = projectItemInstance.Identity;
                string versionString = projectItemInstance.GetProperty("Version");
                result.Add(id, PrunePackageReference.Create(id, versionString));
            }

            return result;
        }

        /// <summary>
        /// Gets the packages path for the specified project.
        /// </summary>
        /// <param name="project">The <see cref="IMSBuildItem" /> representing the project.</param>
        /// <param name="settings">The <see cref="ISettings" /> of the project.</param>
        /// <returns>The full path to the packages directory for the specified project.</returns>
        internal static string GetPackagesPath(IMSBuildProject project, ISettings settings)
        {
            return RestoreSettingsUtils.GetValue(
                () => UriUtility.GetAbsolutePath(project.Directory, project.GetGlobalProperty("RestorePackagesPath")),
                () => UriUtility.GetAbsolutePath(project.Directory, project.GetProperty("RestorePackagesPath")),
                () => SettingsUtility.GetGlobalPackagesFolder(settings));
        }

        /// <summary>
        /// Gets the name of the specified project.
        /// </summary>
        /// <param name="project">The <see cref="IMSBuildItem" /> representing the project.</param>
        /// <returns>The name of the specified project.</returns>
        internal static string GetProjectName(IMSBuildProject project)
        {
            string packageId = project.GetProperty("PackageId");

            if (!string.IsNullOrWhiteSpace(packageId))
            {
                // If the PackageId property was specified, return that
                return packageId;
            }

            string assemblyName = project.GetProperty("AssemblyName");

            if (!string.IsNullOrWhiteSpace(assemblyName))
            {
                // If the AssemblyName property was specified, return that
                return assemblyName;
            }

            // By default return the MSBuildProjectName which is a built-in property that represents the name of the project file without the file extension
            return project.GetProperty("MSBuildProjectName");
        }

        /// <summary>
        /// Gets the project references of the specified project.
        /// </summary>
        /// <param name="project">The <see cref="ProjectInstance" /> to get project references for.</param>
        /// <returns>A <see cref="List{ProjectRestoreReference}" /> containing the project references for the specified project.</returns>
        internal static List<ProjectRestoreReference> GetProjectReferences(IMSBuildProject project)
        {
            // Get the unique list of ProjectReference items that have the ReferenceOutputAssembly metadata set to "true", ignoring duplicates
            var projectReferenceItems = project.GetItems("ProjectReference")
                .Where(i => i.IsPropertyTrue("ReferenceOutputAssembly", defaultValue: true))
                .Distinct(ProjectItemInstanceEvaluatedIncludeComparer.Instance)
                .ToList();

            var projectReferences = new List<ProjectRestoreReference>(projectReferenceItems.Count);

            foreach (var projectReferenceItem in projectReferenceItems)
            {
                string fullPath = projectReferenceItem.GetProperty("FullPath");

                projectReferences.Add(new ProjectRestoreReference
                {
                    ExcludeAssets = GetLibraryIncludeFlags(projectReferenceItem.GetProperty("ExcludeAssets"), LibraryIncludeFlags.None),
                    IncludeAssets = GetLibraryIncludeFlags(projectReferenceItem.GetProperty("IncludeAssets"), LibraryIncludeFlags.All),
                    PrivateAssets = GetLibraryIncludeFlags(projectReferenceItem.GetProperty("PrivateAssets"), LibraryIncludeFlagUtils.DefaultSuppressParent),
                    ProjectPath = fullPath,
                    ProjectUniqueName = fullPath
                });
            }

            return projectReferences;
        }

        /// <summary>
        /// Gets the restore metadata framework information for the specified projects.
        /// </summary>
        /// <param name="projects">A <see cref="IReadOnlyDictionary{NuGetFramework,ProjectInstance}" /> representing the target frameworks and their corresponding projects.</param>
        /// <returns>A <see cref="List{ProjectRestoreMetadataFrameworkInfo}" /> containing the restore metadata framework information for the specified project.</returns>
        internal static List<ProjectRestoreMetadataFrameworkInfo> GetProjectRestoreMetadataFrameworkInfos(List<TargetFrameworkInformation> targetFrameworkInfos, IReadOnlyDictionary<string, IMSBuildProject> projects)
        {
            var projectRestoreMetadataFrameworkInfos = new List<ProjectRestoreMetadataFrameworkInfo>(projects.Count);

            foreach (var targetFrameworkInfo in targetFrameworkInfos)
            {
                var project = projects[targetFrameworkInfo.TargetAlias];
                projectRestoreMetadataFrameworkInfos.Add(new ProjectRestoreMetadataFrameworkInfo(targetFrameworkInfo.FrameworkName)
                {
                    TargetAlias = targetFrameworkInfo.TargetAlias,
                    ProjectReferences = GetProjectReferences(project)
                });
            }

            return projectRestoreMetadataFrameworkInfos;
        }

        /// <summary>
        /// Gets the target frameworks for the specified project.
        /// </summary>
        /// <param name="project">An <see cref="IMSBuildProject" /> representing the main project.</param>
        /// <param name="innerNodes">An <see cref="IReadOnlyDictionary{String,IMSBuildProject}" /> representing all inner projects by their target framework.</param>
        /// <returns></returns>
        internal static IReadOnlyDictionary<string, IMSBuildProject> GetProjectTargetFrameworks(IMSBuildProject project, IReadOnlyDictionary<string, IMSBuildProject> innerNodes)
        {
            var projectFrameworkStrings = GetTargetFrameworkStrings(project);
            var projectTargetFrameworks = new Dictionary<string, IMSBuildProject>();

            if (projectFrameworkStrings.Length > 0)
            {
                foreach (var projectTargetFramework in projectFrameworkStrings)
                {
                    // Attempt to get the corresponding project instance for the target framework.  If one is not found, then the project must not target multiple frameworks
                    // and the main project should be used
                    if (!innerNodes.TryGetValue(projectTargetFramework, out IMSBuildProject innerNode))
                    {
                        innerNode = project;
                    }
                    // Add the target framework and associate it with the project instance to be used for gathering details
                    projectTargetFrameworks[projectTargetFramework] = innerNode;
                }
            }
            else
            {
                // Attempt to get the corresponding project instance for the target framework.  If one is not found, then the project must not target multiple frameworks
                // and the main project should be used
                projectTargetFrameworks[string.Empty] = project;
            }

            return projectTargetFrameworks;
        }

        internal static string[] GetTargetFrameworkStrings(IMSBuildProject project)
        {
            var targetFrameworks = project.GetProperty("TargetFrameworks");
            if (string.IsNullOrEmpty(targetFrameworks))
            {
                targetFrameworks = project.GetProperty("TargetFramework");
            }
            var projectFrameworkStrings = MSBuildStringUtility.Split(targetFrameworks);
            return projectFrameworkStrings;
        }

        /// <summary>
        /// Gets the version of the project.
        /// </summary>
        /// <param name="project">The <see cref="IMSBuildItem" /> representing the project.</param>
        /// <returns>The <see cref="NuGetVersion" /> of the specified project if one was found, otherwise <see cref="PackageSpec.DefaultVersion" />.</returns>
        internal static NuGetVersion GetProjectVersion(IMSBuildItem project)
        {
            string version = project.GetProperty("PackageVersion") ?? project.GetProperty("Version");

            if (version == null)
            {
                return PackageSpec.DefaultVersion;
            }

            return NuGetVersion.Parse(version);
        }

        /// <summary>
        /// Gets the .NET SDK version. If not specified, it will return null.
        /// </summary>
        /// <param name="project">The <see cref="ITargetFramework" /> representing the project.</param>
        /// <returns>The <see cref="NuGetVersion" /> of the .NET SDK if one was found, otherwise <see langword="null">null</see>.</returns>
        internal static NuGetVersion GetSdkVersion(IMSBuildItem project)
        {
            string version = project.GetProperty("NETCoreSdkVersion");

            if (version == null)
            {
                return null;
            }

            return NuGetVersion.Parse(version);
        }

        /// <summary>
        /// Gets the repository path for the specified project.
        /// </summary>
        /// <param name="project">The <see cref="IMSBuildItem" /> representing the project.</param>
        /// <param name="settings">The <see cref="ISettings" /> of the specified project.</param>
        /// <returns>The repository path of the specified project.</returns>
        internal static string GetRepositoryPath(IMSBuildProject project, ISettings settings)
        {
            return RestoreSettingsUtils.GetValue(
                () => UriUtility.GetAbsolutePath(project.Directory, project.GetGlobalProperty("RestoreRepositoryPath")),
                () => UriUtility.GetAbsolutePath(project.Directory, project.GetProperty("RestoreRepositoryPath")),
                () => SettingsUtility.GetRepositoryPath(settings),
                () =>
                {
                    string solutionDir = project.GetProperty("SolutionPath");

                    solutionDir = string.Equals(solutionDir, "*Undefined*", StringComparison.OrdinalIgnoreCase)
                        ? project.Directory
                        : Path.GetDirectoryName(solutionDir);

                    return UriUtility.GetAbsolutePath(solutionDir, PackagesConfig.PackagesNodeName);
                });
        }

        /// <summary>
        /// Gets the restore output path for the specified project.
        /// </summary>
        /// <param name="project">The <see cref="IMSBuildItem" /> representing the project.</param>
        /// <returns>The full path to the restore output directory for the specified project if a value is specified, otherwise <code>null</code>.</returns>
        internal static string GetRestoreOutputPath(IMSBuildProject project)
        {
            string outputPath = project.GetProperty("RestoreOutputPath") ?? project.GetProperty("MSBuildProjectExtensionsPath");

            return outputPath == null ? null : Path.GetFullPath(Path.Combine(project.Directory, outputPath));
        }

        /// <summary>
        /// Gets the package sources of the specified project.
        /// </summary>
        /// <param name="project">An <see cref="IMSBuildItem" /> representing the project..</param>
        /// <param name="innerNodes">An <see cref="IReadOnlyCollection{IMSBuildItem}" /> containing the inner nodes of the project if its targets multiple frameworks.</param>
        /// <param name="settings">The <see cref="ISettings" /> of the specified project.</param>
        /// <returns>A <see cref="List{PackageSource}" /> object containing the packages sources for the specified project.</returns>
        internal static List<PackageSource> GetSources(IMSBuildProject project, IReadOnlyCollection<IMSBuildProject> innerNodes, ISettings settings)
        {
            return BuildTasksUtility.GetSources(
                project.GetGlobalProperty("OriginalMSBuildStartupDirectory"),
                project.Directory,
                project.SplitPropertyValueOrNull("RestoreSources"),
                project.SplitGlobalPropertyValueOrNull("RestoreSources"),
                innerNodes.SelectMany(i => MSBuildStringUtility.Split(i.GetProperty("RestoreAdditionalProjectSources"))),
                settings)
                .Select(i => new PackageSource(i))
                .ToList();
        }

        /// <summary>
        /// Gets a value indicating if the specified project is a legacy project.
        /// </summary>
        /// <param name="project">The <see cref="IMSBuildItem" /> representing the project.</param>
        /// <returns><code>true</code> if the specified project is considered legacy, otherwise <code>false</code>.</returns>
        internal static bool IsLegacyProject(IMSBuildItem project)
        {
            // We consider the project to be legacy if it does not specify TargetFramework or TargetFrameworks
            return project.GetProperty("TargetFramework") == null && project.GetProperty("TargetFrameworks") == null;
        }

        /// <summary>
        /// Determines of the specified option is <code>true</code>.
        /// </summary>
        /// <param name="name">The name of the option.</param>
        /// <param name="options">A <see cref="Dictionary{String,String}" />containing options.</param>
        /// <returns><code>true</code> if the specified option is true, otherwise <code>false</code>.</returns>
        internal static bool IsOptionTrue(string name, IReadOnlyDictionary<string, string> options)
        {
            return options.TryGetValue(name, out string value) && StringComparer.OrdinalIgnoreCase.Equals(value, bool.TrueString);
        }

        /// <summary>
        /// Gets the <see cref="LibraryIncludeFlags" /> for the specified value.
        /// </summary>
        /// <param name="value">A semicolon delimited list of include flags.</param>
        /// <param name="defaultValue">The default value ot return if the value contains no flags.</param>
        /// <returns>The <see cref="LibraryIncludeFlags" /> for the specified value, otherwise the <paramref name="defaultValue" />.</returns>
        private static LibraryIncludeFlags GetLibraryIncludeFlags(string value, LibraryIncludeFlags defaultValue)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return defaultValue;
            }

            string[] parts = MSBuildStringUtility.Split(value);

            return parts.Length > 0 ? LibraryIncludeFlagUtils.GetFlags(parts) : defaultValue;
        }

        /// <summary>
        /// Gets the list of project graph entry points.  If the entry project is a solution, this method returns all of the projects it contains.
        /// </summary>
        /// <param name="entryProjectPath">The full path to the main project or solution file.</param>
        /// <param name="globalProperties">An <see cref="IDictionary{String,String}" /> representing the global properties for the project.</param>
        /// <returns></returns>
        private List<ProjectGraphEntryPoint> GetProjectGraphEntryPoints(string entryProjectPath, IDictionary<string, string> globalProperties)
        {
            // If the project's extension is .sln, parse it as a Visual Studio solution and return the projects it contains
            var extension = Path.GetExtension(entryProjectPath);
            if (string.Equals(extension, ".sln", StringComparison.OrdinalIgnoreCase)
                || string.Equals(extension, ".slnx", StringComparison.OrdinalIgnoreCase))
            {
                var solutionFile = SolutionFile.Parse(entryProjectPath);

                IEnumerable<ProjectInSolution> projectsKnownToMSBuild = solutionFile.ProjectsInOrder.Where(i => i.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat);
                IEnumerable<ProjectInSolution> projectsNotKnownToMSBuild = solutionFile.ProjectsInOrder.Except(projectsKnownToMSBuild);

                if (projectsNotKnownToMSBuild.Any())
                {
                    IList<string> projects = projectsNotKnownToMSBuild.Select(project => project.ProjectName).ToList();

                    MSBuildLogger.LogInformation(string.Format(CultureInfo.CurrentCulture,
                        Strings.Log_ProjectsInSolutionNotKnowntoMSBuild,
                        projects.Count, string.Join(",", projects)));
                }

                return projectsKnownToMSBuild.Select(i => new ProjectGraphEntryPoint(i.AbsolutePath, globalProperties)).ToList();
            }

            // Return just the main project in a list if its not a solution file
            return new List<ProjectGraphEntryPoint>
            {
                new ProjectGraphEntryPoint(entryProjectPath, globalProperties),
            };
        }

        /// <summary>
        /// Gets the target framework information for the specified project.  This includes the package references, package downloads, and framework references.
        /// </summary>
        /// <param name="projectInnerNodes">An <see cref="IReadOnlyDictionary{NuGetFramework,ProjectInstance} "/> containing the projects by their target framework.</param>
        /// <param name="isCpvmEnabled">A flag that is true if the Central Package Management was enabled.</param>
        /// <param name="isPruningEnabledGlobally">A flag that tells us the default for pruning, if the pruning property is not set.</param>
        /// <returns>A <see cref="List{TargetFrameworkInformation}" /> containing the target framework information for the specified project.</returns>
        internal static List<TargetFrameworkInformation> GetTargetFrameworkInfos(IReadOnlyDictionary<string, IMSBuildProject> projectInnerNodes, bool isCpvmEnabled, bool isPruningEnabledGlobally)
        {
            var targetFrameworkInfos = new List<TargetFrameworkInformation>(projectInnerNodes.Count);

            foreach (var projectInnerNode in projectInnerNodes)
            {
                var msBuildProjectInstance = projectInnerNode.Value;
                var targetAlias = string.IsNullOrEmpty(projectInnerNode.Key) ? string.Empty : projectInnerNode.Key;

                NuGetFramework targetFramework = MSBuildProjectFrameworkUtility.GetProjectFramework(
                    projectFilePath: projectInnerNode.Value.FullPath,
                    targetFrameworkMoniker: msBuildProjectInstance.GetProperty("TargetFrameworkMoniker"),
                    targetPlatformMoniker: msBuildProjectInstance.GetProperty("TargetPlatformMoniker"),
                    targetPlatformMinVersion: msBuildProjectInstance.GetProperty("TargetPlatformMinVersion"),
                    clrSupport: msBuildProjectInstance.GetProperty("CLRSupport"),
                    windowsTargetPlatformMinVersion: msBuildProjectInstance.GetProperty("WindowsTargetPlatformMinVersion"));

                var packageTargetFallback = MSBuildStringUtility.Split(msBuildProjectInstance.GetProperty("PackageTargetFallback")).Select(NuGetFramework.Parse).ToList();

                var assetTargetFallbackEnum = MSBuildStringUtility.Split(msBuildProjectInstance.GetProperty(nameof(TargetFrameworkInformation.AssetTargetFallback))).Select(NuGetFramework.Parse).ToList();

                AssetTargetFallbackUtility.EnsureValidFallback(packageTargetFallback, assetTargetFallbackEnum, msBuildProjectInstance.FullPath);

                (targetFramework, ImmutableArray<NuGetFramework> imports, bool assetTargetFallback, bool warn) = AssetTargetFallbackUtility.GetFallbackFrameworkInformation(targetFramework, packageTargetFallback, assetTargetFallbackEnum);

                IReadOnlyDictionary<string, CentralPackageVersion> centralPackageVersions = null;
                if (isCpvmEnabled)
                {
                    centralPackageVersions = GetCentralPackageVersions(msBuildProjectInstance);
                }

                var dependencies = GetPackageReferences(msBuildProjectInstance, isCpvmEnabled, centralPackageVersions);

                bool? restoreEnablePackagePruning = MSBuildStringUtility.GetBooleanOrNull(msBuildProjectInstance.GetProperty("RestoreEnablePackagePruning"));
                bool isPackagePruningEnabled = restoreEnablePackagePruning == null ? isPruningEnabledGlobally : restoreEnablePackagePruning == true;
                var prunedReferences = isPackagePruningEnabled ? GetPrunePackageReferences(msBuildProjectInstance) : [];

                var targetFrameworkInformation = new TargetFrameworkInformation()
                {
                    AssetTargetFallback = assetTargetFallback,
                    CentralPackageVersions = centralPackageVersions,
                    Dependencies = dependencies,
                    DownloadDependencies = GetPackageDownloads(msBuildProjectInstance).ToImmutableArray(),
                    FrameworkName = targetFramework,
                    Imports = imports,
                    FrameworkReferences = GetFrameworkReferences(msBuildProjectInstance),
                    PackagesToPrune = prunedReferences,
                    RuntimeIdentifierGraphPath = msBuildProjectInstance.GetProperty(nameof(TargetFrameworkInformation.RuntimeIdentifierGraphPath)),
                    TargetAlias = targetAlias,
                    Warn = warn
                };

                targetFrameworkInfos.Add(targetFrameworkInformation);
            }

            return targetFrameworkInfos;
        }

        /// <summary>
        /// Gets a <see cref="DependencyGraphSpec" /> for the specified project.
        /// </summary>
        /// <param name="entryProjectPath">The full path to a project or Visual Studio Solution File.</param>
        /// <param name="globalProperties">An <see cref="IDictionary{String,String}" /> containing the global properties to use when evaluation MSBuild projects.</param>
        /// <param name="interactive"><see langword="true" /> if the build is allowed to interact with the user, otherwise <see langword="false" />.</param>
        /// <returns>A <see cref="DependencyGraphSpec" /> for the specified project if they could be loaded, otherwise <code>null</code>.</returns>
        private DependencyGraphSpec GetDependencyGraphSpec(string entryProjectPath, IDictionary<string, string> globalProperties, bool interactive, string binaryLoggerParameters, IEnvironmentVariableReader environmentVariableReader)
        {
            string envVar = environmentVariableReader.GetEnvironmentVariable(PackageSpecFactory.EnvironmentVariableName);
            if (!string.Equals(envVar, bool.FalseString, StringComparison.OrdinalIgnoreCase))
            {
                return GetDependencyGraphSpec(
                    entryProjectPath,
                    globalProperties,
                    interactive,
                    binaryLoggerParameters,
                    createProjectFactory: static (string projectPath, (ProjectInstance projectInstance, string targetFramework) args) =>
                    {
                        var adapter = new RestoreProjectAdapter(args.projectInstance.FullPath, args.projectInstance.GlobalProperties);
                        adapter.AddTargetFramework(args.targetFramework, new TargetFrameworkAdapter(args.projectInstance));
                        return adapter;
                    },
                    updateProjectFactory: static (string projectPath, RestoreProjectAdapter project, (ProjectInstance projectInstance, string targetFramework) args) =>
                    {
                        project.AddTargetFramework(args.targetFramework, new TargetFrameworkAdapter(args.projectInstance));
                        return project;
                    },
                    projectFinalizeDelegate: static project => project.Prepare(),
                    getPackageSpec: project =>
                    {
                        var settings = RestoreSettingsUtils.ReadSettings(
                            project.OuterBuild.GetProperty("RestoreSolutionDirectory"),
                            project.OuterBuild.GetProperty("RestoreRootConfigDirectory") ?? project.Directory,
                            UriUtility.GetAbsolutePath(project.Directory, project.OuterBuild.GetProperty("RestoreConfigFile")),
                            MachineWideSettingsLazy,
                            _settingsLoadContext);

                        var packageSpec = PackageSpecFactory.GetPackageSpec(project, settings);
                        return packageSpec;
                    });
            }
            else
            {
                // Delete this code path once PackageSpecFactory.GetPackageSpec has been tested long enough to trust.
                return GetDependencyGraphSpec(
                    entryProjectPath,
                    globalProperties,
                    interactive,
                    binaryLoggerParameters,
                    createProjectFactory: static (string projectPath, (ProjectInstance projectInstance, string targetFramework) args) =>
                        new ProjectWithInnerNodes(args.targetFramework, new MSBuildProjectInstance(args.projectInstance)),
                    updateProjectFactory: static (string projectPath, ProjectWithInnerNodes project, (ProjectInstance projectInstance, string targetFramework) args) =>
                        project.Add(args.targetFramework, new MSBuildProjectInstance(args.projectInstance)),
                    projectFinalizeDelegate: null,
                    getPackageSpec: project =>
                    {
                        var packageSpec = GetPackageSpec(project.OuterProject, project);
                        return packageSpec;
                    });
            }
        }

        private DependencyGraphSpec GetDependencyGraphSpec<TProject>(
            string entryProjectPath,
            IDictionary<string, string> globalProperties,
            bool interactive,
            string binaryLoggerParameters,
            Func<string, (ProjectInstance, string), TProject> createProjectFactory,
            Func<string, TProject, (ProjectInstance, string), TProject> updateProjectFactory,
            Action<TProject> projectFinalizeDelegate,
            Func<TProject, PackageSpec> getPackageSpec)
        {
            try
            {
                MSBuildLogger.LogMinimal(Strings.DeterminingProjectsToRestore);

                var entryProjects = GetProjectGraphEntryPoints(entryProjectPath, globalProperties);

                // Load the projects via MSBuild and create an array of them since Parallel.ForEach is optimized for arrays
                var projects = LoadProjects(entryProjects, interactive, binaryLoggerParameters, createProjectFactory, updateProjectFactory, projectFinalizeDelegate);

                // If no projects were loaded, return an empty DependencyGraphSpec
                if (projects == null || projects.Count == 0)
                {
                    return new DependencyGraphSpec();
                }

                var sw = Stopwatch.StartNew();

                var dependencyGraphSpec = new DependencyGraphSpec(isReadOnly: true);

                // 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 projectPathLookup = new ConcurrentDictionary<string, string>(uniqueNameComparer);

                try
                {
                    // Get the PackageSpecs in parallel because creating each one is relatively expensive so parallelism speeds things up
                    Parallel.ForEach(projects, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, project =>
                    {
                        PackageSpec packageSpec = getPackageSpec(project.Value);

                        if (packageSpec != null)
                        {
                            // Keep track of all project path casings
                            var uniqueName = packageSpec.RestoreMetadata.ProjectUniqueName;
                            if (uniqueName != null && !projectPathLookup.ContainsKey(uniqueName))
                            {
                                projectPathLookup.TryAdd(uniqueName, uniqueName);
                            }

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

                            lock (dependencyGraphSpec)
                            {
                                dependencyGraphSpec.AddProject(packageSpec);
                            }
                        }
                    });
                }
#pragma warning disable CA1031 // Do not catch general exception types
                catch (Exception e)
#pragma warning restore CA1031 // Do not catch general exception types
                {
                    LogErrorFromException(e);

                    return null;
                }

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

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

                // Add all entry projects if they support restore.  In most cases this is just a single project but if the entry
                // project is a solution, then all projects in the solution are added (if they support restore)
                foreach (var entryPoint in entryProjects)
                {
                    PackageSpec project = dependencyGraphSpec.GetProjectSpec(entryPoint.ProjectFile);

                    if (project != null && BuildTasksUtility.DoesProjectSupportRestore(project))
                    {
                        dependencyGraphSpec.AddRestore(entryPoint.ProjectFile);
                    }
                }

                sw.Stop();

                MSBuildLogger.LogDebug(string.Format(CultureInfo.CurrentCulture, Strings.CreatedDependencyGraphSpec, sw.ElapsedMilliseconds));

                return dependencyGraphSpec;
            }
#pragma warning disable CA1031 // Do not catch general exception types
            catch (Exception e)
#pragma warning restore CA1031 // Do not catch general exception types
            {
                LogErrorFromException(e);
            }

            return null;
        }

        /// <summary>
        /// Gets a <see cref="PackageSpec" /> for the specified project.
        /// </summary>
        /// <param name="project">An <see cref="IMSBuildProject" /> object that represents the project.</param>
        /// <param name="allInnerNodes">An <see cref="IReadOnlyDictionary{String,IMSBuildProject}" /> that represents all inner projects by their target framework.</param>
        /// <returns></returns>
        private PackageSpec GetPackageSpec(IMSBuildProject project, IReadOnlyDictionary<string, IMSBuildProject> allInnerNodes)
        {
            var settings = RestoreSettingsUtils.ReadSettings(
                project.GetProperty("RestoreSolutionDirectory"),
                project.GetProperty("RestoreRootConfigDirectory") ?? project.Directory,
                UriUtility.GetAbsolutePath(project.Directory, project.GetProperty("RestoreConfigFile")),
                MachineWideSettingsLazy,
                _settingsLoadContext);

            // Get the target frameworks for the project and the project instance for each framework
            var projectsByTargetFramework = GetProjectTargetFrameworks(project, allInnerNodes);

            (ProjectRestoreMetadata restoreMetadata, List<TargetFrameworkInformation> targetFrameworkInfos) = GetProjectRestoreMetadataAndTargetFrameworkInformation(project, projectsByTargetFramework, settings);

            if (restoreMetadata == null || targetFrameworkInfos == null)
            {
                return null;
            }

            var packageSpec = new PackageSpec(targetFrameworkInfos)
            {
                FilePath = project.FullPath,
                Name = restoreMetadata.ProjectName,
                RestoreMetadata = restoreMetadata,
                RuntimeGraph = new RuntimeGraph(
                    MSBuildStringUtility.Split($"{project.GetProperty("RuntimeIdentifiers")};{project.GetProperty("RuntimeIdentifier")}")
                        .Concat(projectsByTargetFramework.Values.SelectMany(i => MSBuildStringUtility.Split($"{i.GetProperty("RuntimeIdentifiers")};{i.GetProperty("RuntimeIdentifier")}")))
                        .Distinct(StringComparer.Ordinal)
                        .Select(rid => new RuntimeDescription(rid))
                        .ToList(),
                    MSBuildStringUtility.Split(project.GetProperty("RuntimeSupports"))
                        .Distinct(StringComparer.Ordinal)
                        .Select(s => new CompatibilityProfile(s))
                        .ToList()
                    ),
                Version = GetProjectVersion(project),
                RestoreSettings = new ProjectRestoreSettings()
                {
                    SdkVersion = GetSdkVersion(project)
                }
            };

            return packageSpec;
        }

        /// <summary>
        /// Gets the restore metadata and target framework information for the specified project.
        /// </summary>
        /// <param name="project">An <see cref="IMSBuildProject" /> representing the project.</param>
        /// <param name="projectsByTargetFramework">A <see cref="IReadOnlyDictionary{NuGetFramework,IMSBuildProject}" /> containing the inner nodes by target framework.</param>
        /// <param name="settings">The <see cref="ISettings" /> of the specified project.</param>
        /// <returns>A <see cref="Tuple" /> containing the <see cref="ProjectRestoreMetadata" /> and <see cref="List{TargetFrameworkInformation}" /> for the specified project.</returns>
        private (ProjectRestoreMetadata RestoreMetadata, List<TargetFrameworkInformation> TargetFrameworkInfos) GetProjectRestoreMetadataAndTargetFrameworkInformation(IMSBuildProject project, IReadOnlyDictionary<string, IMSBuildProject> projectsByTargetFramework, ISettings settings)
        {
            string projectName = GetProjectName(project);

            string outputPath = GetRestoreOutputPath(project);

            (ProjectStyle projectStyle, string packagesConfigFilePath) = GetProjectStyle(project, projectsByTargetFramework, MSBuildLogger);

            (bool isCentralPackageManagementEnabled, bool isCentralPackageVersionOverrideDisabled, bool isCentralPackageTransitivePinningEnabled, bool isCentralPackageFloatingVersionsEnabled) = MSBuildRestoreUtility.GetCentralPackageManagementSettings(project, projectStyle);

            bool isPruningEnabledGlobally = GetPackagePruningDefault(projectsByTargetFramework.Values);
            RestoreAuditProperties auditProperties = MSBuildRestoreUtility.GetRestoreAuditProperties(project, projectsByTargetFramework.Values, GetAuditSuppressions(project));

            List<TargetFrameworkInformation> targetFrameworkInfos = GetTargetFrameworkInfos(projectsByTargetFramework, isCentralPackageManagementEnabled, isPruningEnabledGlobally);

            List<IMSBuildProject> innerNodes = projectsByTargetFramework.Values.ToList();

            ProjectRestoreMetadata restoreMetadata;

            if (projectStyle == ProjectStyle.PackagesConfig)
            {
                restoreMetadata = new PackagesConfigProjectRestoreMetadata
                {
                    PackagesConfigPath = packagesConfigFilePath,
                    RepositoryPath = GetRepositoryPath(project, settings),
                    RestoreAuditProperties = auditProperties,
                };
            }
            else
            {
                restoreMetadata = new ProjectRestoreMetadata
                {
                    // CrossTargeting is on, even if the TargetFrameworks property has only 1 tfm.
                    CrossTargeting = (projectStyle == ProjectStyle.PackageReference) && (
                        projectsByTargetFramework.Count > 1 || !string.IsNullOrWhiteSpace(project.GetProperty("TargetFrameworks"))),
                    FallbackFolders = BuildTasksUtility.GetFallbackFolders(
                        project.GetProperty("MSBuildStartupDirectory"),
                        project.Directory,
                        project.SplitPropertyValueOrNull("RestoreFallbackFolders"),
                        project.SplitGlobalPropertyValueOrNull("RestoreFallbackFolders"),
                        innerNodes.SelectMany(i => MSBuildStringUtility.Split(i.GetProperty("RestoreAdditionalProjectFallbackFolders"))),
                        innerNodes.SelectMany(i => MSBuildStringUtility.Split(i.GetProperty("RestoreAdditionalProjectFallbackFoldersExcludes"))),
                        settings),
                    SkipContentFileWrite = IsLegacyProject(project),
                    ValidateRuntimeAssets = project.IsPropertyTrue("ValidateRuntimeIdentifierCompatibility"),
                    CentralPackageVersionsEnabled = isCentralPackageManagementEnabled && projectStyle == ProjectStyle.PackageReference,
                    CentralPackageFloatingVersionsEnabled = isCentralPackageFloatingVersionsEnabled,
                    CentralPackageVersionOverrideDisabled = isCentralPackageVersionOverrideDisabled,
                    CentralPackageTransitivePinningEnabled = isCentralPackageTransitivePinningEnabled,
                    RestoreAuditProperties = auditProperties
                };
            }

            restoreMetadata.CacheFilePath = NoOpRestoreUtilities.GetProjectCacheFilePath(outputPath, project.FullPath);
            restoreMetadata.ConfigFilePaths = settings.GetConfigFilePaths();
            restoreMetadata.OutputPath = outputPath;
            targetFrameworkInfos.ForEach(tfi =>
                restoreMetadata.OriginalTargetFrameworks.Add(
                        !string.IsNullOrEmpty(tfi.TargetAlias) ?
                            tfi.TargetAlias :
                            tfi.FrameworkName.GetShortFolderName()));
            restoreMetadata.PackagesPath = GetPackagesPath(project, settings);
            restoreMetadata.ProjectName = projectName;
            restoreMetadata.ProjectPath = project.FullPath;
            restoreMetadata.ProjectStyle = projectStyle;
            restoreMetadata.ProjectUniqueName = project.FullPath;
            restoreMetadata.ProjectWideWarningProperties = WarningProperties.GetWarningProperties(project.GetProperty("TreatWarningsAsErrors"), project.GetProperty("WarningsAsErrors"), project.GetProperty("NoWarn"), project.GetProperty("WarningsNotAsErrors"));
            restoreMetadata.RestoreLockProperties = new RestoreLockProperties(project.GetProperty("RestorePackagesWithLockFile"), project.GetProperty("NuGetLockFilePath"), project.IsPropertyTrue("RestoreLockedMode"));
            restoreMetadata.Sources = GetSources(project, innerNodes, settings);
            restoreMetadata.TargetFrameworks = GetProjectRestoreMetadataFrameworkInfos(targetFrameworkInfos, projectsByTargetFramework);
            restoreMetadata.UsingMicrosoftNETSdk = MSBuildRestoreUtility.GetUsingMicrosoftNETSdk(project.GetProperty("UsingMicrosoftNETSdk"));
            restoreMetadata.SdkAnalysisLevel = MSBuildRestoreUtility.GetSdkAnalysisLevel(project.GetProperty("SdkAnalysisLevel"));
            restoreMetadata.UseLegacyDependencyResolver = project.IsPropertyTrue("RestoreUseLegacyDependencyResolver");
            restoreMetadata.RestoreDoNotWriteDependencyGraphSpec = project.IsPropertyTrue("RestoreDoNotWriteDependencyGraphSpec");

            return (restoreMetadata, targetFrameworkInfos);

            static (ProjectStyle, string packagesConfigPath) GetProjectStyle(IMSBuildProject project, IReadOnlyDictionary<string, IMSBuildProject> tfms, Common.ILogger log)
            {
                ProjectStyle? projectStyleOrNull = BuildTasksUtility.GetProjectRestoreStyleFromProjectProperty(project.GetProperty("RestoreProjectStyle"));
                bool hasPackageReferenceItems = tfms.Values.Any(p => p.GetItems("PackageReference").Any());
                (ProjectStyle ProjectStyle, string PackagesConfigFilePath) projectStyleResult = BuildTasksUtility.GetProjectRestoreStyle(
                    restoreProjectStyle: projectStyleOrNull,
                    hasPackageReferenceItems: hasPackageReferenceItems,
                    projectDirectory: project.Directory,
                    projectName: project.GetProperty("MSBuildProjectName"),
                    log: log);

                return (projectStyleResult.ProjectStyle, projectStyleResult.PackagesConfigFilePath);
            }
        }

        internal static bool GetPackagePruningDefault(IEnumerable<IMSBuildProject> innerBuilds)
        {
            foreach (var item in innerBuilds.NoAllocEnumerate())
            {
                if (item.IsPropertyTrue("RestorePackagePruningDefault"))
                {
                    return true;
                }
            }
            return false;
        }

        private static HashSet<string> GetAuditSuppressions(IMSBuildProject project)
        {
            IEnumerable<string> suppressions = GetDistinctItemsOrEmpty(project, "NuGetAuditSuppress")
                                                    .Select(i => i.Identity);

            return suppressions?.Count() > 0 ? new HashSet<string>(suppressions) : null;
        }

        /// <summary>
        /// Recursively loads and evaluates MSBuild projects.
        /// </summary>
        /// <param name="entryProjects">An <see cref="IEnumerable{ProjectGraphEntryPoint}" /> containing the entry projects to load.</param>
        /// <param name="interactive"><see langword="true" /> if the build is allowed to interact with the user, otherwise <see langword="false" />.</param>
        /// <param name="binaryLoggerParameters">Optional parameters to use for the MSBuild binary log.</param>
        /// <param name="createProjectFactory">A factory method that creates a project adapter from an MSBuild ProjectInstance.</param>
        /// <param name="updateProjectFactory">A factory method that updates a project adapter with a target framework and MSBuild ProjectInstance.</param>
        /// <param name="projectFinalizeDelegate">An option delegate to finalize a project adapter once all projects have been evaluated.</param>
        /// <returns>An <see cref="ICollection{ProjectWithInnerNodes}" /> object containing projects and their inner nodes if they are targeting multiple frameworks.</returns>
        private ConcurrentDictionary<string, TProject> LoadProjects<TProject>(
            IEnumerable<ProjectGraphEntryPoint> entryProjects,
            bool interactive,
            string binaryLoggerParameters,
            Func<string, (ProjectInstance, string), TProject> createProjectFactory,
            Func<string, TProject, (ProjectInstance, string), TProject> updateProjectFactory,
            Action<TProject> projectFinalizeDelegate)
        {
            try
            {
                var loggers = new List<Microsoft.Build.Framework.ILogger>
                {
                    LoggingQueue
                };

                bool logTaskInputs = false;

                // Attach the binary logger if parameters were specified
                if (!string.IsNullOrWhiteSpace(binaryLoggerParameters))
                {
                    loggers.Add(new BinaryLogger
                    {
                        Parameters = Uri.UnescapeDataString(binaryLoggerParameters)
                    });

                    // Log task inputs when the binary logger is attached
                    logTaskInputs = true;
                }

                var projects = new ConcurrentDictionary<string, TProject>(PathUtility.GetStringComparerBasedOnOS());

                using var projectCollection = new ProjectCollection(
                    globalProperties: null,
                    // Attach a logger for evaluation only if the Debug option is set
                    loggers: loggers,
                    remoteLoggers: null,
                    toolsetDefinitionLocations: ToolsetDefinitionLocations.Default,
                    // Having more than 1 node spins up multiple msbuild.exe instances to run builds in parallel
                    // However, these targets complete so quickly that the added overhead makes it take longer
                    maxNodeCount: 1,
                    onlyLogCriticalEvents: false,
                    // Loading projects as readonly makes parsing a little faster since comments and whitespace can be ignored
                    loadProjectsReadOnly: true);

                Stopwatch sw = Stopwatch.StartNew();

                EvaluationContext evaluationContext = EvaluationContext.Create(EvaluationContext.SharingPolicy.Shared);

                // Create a ProjectGraph object and pass a factory method which creates a ProjectInstance
                ProjectGraph projectGraph = new ProjectGraph(entryProjects, projectCollection, (path, properties, collection) =>
                {
                    var projectOptions = new ProjectOptions
                    {
                        EvaluationContext = evaluationContext,
                        GlobalProperties = properties,
                        Interactive = interactive,
                        // Ignore bad imports to maximize the chances of being able to load the project and restore
                        LoadSettings = ProjectLoadSettings.IgnoreEmptyImports | ProjectLoadSettings.IgnoreInvalidImports | ProjectLoadSettings.IgnoreMissingImports | ProjectLoadSettings.DoNotEvaluateElementsWithFalseCondition,
                        ProjectCollection = collection
                    };

                    return ProjectInstance.FromFile(path, projectOptions);
                });

                int buildCount = 0;
                int failedBuildSubmissionCount = 0;

                var buildParameters = new BuildParameters(projectCollection)
                {
                    // Use the same loggers as the project collection
                    Loggers = projectCollection.Loggers,
                    LogTaskInputs = logTaskInputs
                };

                try
                {
                    // BeginBuild starts a queue which accepts build requests and applies the build parameters to all of them
                    BuildManager.DefaultBuildManager.BeginBuild(buildParameters);

                    // Loop through each project and run the targets.  There is no need for this to run in parallel since there is only
                    // one node in the process to run builds.
                    foreach (ProjectGraphNode projectGraphItem in projectGraph.ProjectNodes)
                    {
                        ProjectInstance projectInstance = projectGraphItem.ProjectInstance;

                        if (!projectInstance.Targets.ContainsKey("_IsProjectRestoreSupported") || projectInstance.GlobalProperties == null || projectInstance.GlobalProperties.TryGetValue("TargetFramework", out string targetFramework) && string.IsNullOrWhiteSpace(targetFramework))
                        {
                            // In rare cases, users can set an empty TargetFramework value in a project-to-project reference.  Static Graph will respect that
                            // but NuGet does not need to do anything with that instance of the project since the actual project is still loaded correctly
                            // with its actual TargetFramework.
                            var message = MSBuildRestoreUtility.GetMessageForUnsupportedProject(projectInstance.FullPath);
                            MSBuildLogger.Log(message);
                            continue;
                        }

                        // If the project supports restore, queue up a build of the targets needed for restore
                        BuildSubmission buildSubmission = BuildManager.DefaultBuildManager.PendBuildRequest(
                            new BuildRequestData(
                                projectInstance,
                                TargetsToBuild,
                                hostServices: null,
                                // Suppresses an error that a target does not exist because it may or may not contain the targets that we're running
                                BuildRequestDataFlags.SkipNonexistentTargets));

                        buildSubmission.ExecuteAsync((submission) =>
                        {
                            BuildResult result = submission.BuildResult;
                            if (result.OverallResult == BuildResultCode.Failure)
                            {
                                failedBuildSubmissionCount++;
                            }

                            buildCount++;

                            projects.AddOrUpdate(
                                projectInstance.FullPath,
                                createProjectFactory,
                                updateProjectFactory,
                                (projectInstance, targetFramework));
                        }, context: null);
                    }
                }
                finally
                {
                    // EndBuild blocks until all builds are complete
                    BuildManager.DefaultBuildManager.EndBuild();
                }

                sw.Stop();

                if (projectFinalizeDelegate is not null)
                {
                    foreach (var kvp in projects)
                    {
                        projectFinalizeDelegate(kvp.Value);
                    }
                }

                MSBuildLogger.LogInformation(string.Format(CultureInfo.CurrentCulture, Strings.ProjectEvaluationSummary, projectGraph.ProjectNodes.Count, sw.ElapsedMilliseconds, buildCount, failedBuildSubmissionCount));

                if (failedBuildSubmissionCount != 0)
                {
                    // Return null if any builds failed, they will have logged errors
                    return null;
                }

                // Just return the projects not the whole dictionary as it was just used to group the projects together
                return projects;
            }
#pragma warning disable CA1031 // Do not catch general exception types
            catch (Exception e)
#pragma warning restore CA1031 // Do not catch general exception types
            {
                LogErrorFromException(e);

                return null;
            }
        }

        /// <summary>
        /// Returns the list of distinct items with the <paramref name="itemName"/> name.
        /// Two items are equal if they have the same <see cref="IMSBuildItem.Identity"/>.
        /// </summary>
        /// <param name="project">The project.</param>
        /// <param name="itemName">The item name.</param>
        /// <returns>Returns the list of items with the <paramref name="itemName"/>. If the item does not exist it will return an empty list.</returns>
        private static IEnumerable<IMSBuildItem> GetDistinctItemsOrEmpty(IMSBuildProject project, string itemName)
        {
            return project.GetItems(itemName)?.Distinct(ProjectItemInstanceEvaluatedIncludeComparer.Instance) ?? Enumerable.Empty<IMSBuildItem>();
        }

        /// <summary>
        /// Logs an error from the specified exception.
        /// </summary>
        /// <param name="exception">The <see cref="Exception" /> with details to be logged.</param>
        private void LogErrorFromException(Exception exception)
        {
            switch (exception)
            {
                case AggregateException aggregateException:
                    foreach (Exception innerException in aggregateException.Flatten().InnerExceptions)
                    {
                        LogErrorFromException(innerException);
                    }
                    break;

                case InvalidProjectFileException invalidProjectFileException:
                    // Special case the InvalidProjectFileException since it has extra information about what project file couldn't be loaded
                    LoggingQueue.TaskLoggingHelper.LogError(
                        invalidProjectFileException.ErrorSubcategory,
                        invalidProjectFileException.ErrorCode,
                        invalidProjectFileException.HelpKeyword,
                        invalidProjectFileException.ProjectFile,
                        invalidProjectFileException.LineNumber,
                        invalidProjectFileException.ColumnNumber,
                        invalidProjectFileException.EndLineNumber,
                        invalidProjectFileException.EndColumnNumber,
                        invalidProjectFileException.Message);
                    break;

                default:
                    LoggingQueue.TaskLoggingHelper.LogErrorFromException(
                        exception,
                        showStackTrace: true);
                    break;
            }
        }

        /// <summary>
        /// Logs the list of files to embed in the MSBuild binary log.
        /// </summary>
        /// <param name="dependencyGraphSpec"></param>
        private void LogFilesToEmbedInBinlog(DependencyGraphSpec dependencyGraphSpec, IReadOnlyDictionary<string, string> options)
        {
            // Determines what the user wants embedded in the binary log where 0 or false disables embedding anything, 2 embeds everything, and 1 or true embeds just the assets file, g.props, and g.targets.
            options.TryGetValue(nameof(RestoreTaskEx.EmbedFilesInBinlog), out string embedFilesInBinlog);

            int embedInBinlogSelection = BuildTasksUtility.GetFilesToEmbedInBinlogValue(embedFilesInBinlog);

            if (embedInBinlogSelection == 0)
            {
                return;
            }

            IReadOnlyList<PackageSpec> projects = dependencyGraphSpec.Projects;

            foreach (PackageSpec project in projects)
            {
                if (project.RestoreMetadata.ProjectStyle == ProjectStyle.PackageReference)
                {
                    LoggingQueue.Enqueue(new ConsoleOutLogEmbedInBinlog(Path.Combine(project.RestoreMetadata.OutputPath, LockFileFormat.AssetsFileName)));
                    LoggingQueue.Enqueue(new ConsoleOutLogEmbedInBinlog(BuildAssetsUtils.GetMSBuildFilePathForPackageReferenceStyleProject(project, BuildAssetsUtils.PropsExtension)));
                    LoggingQueue.Enqueue(new ConsoleOutLogEmbedInBinlog(BuildAssetsUtils.GetMSBuildFilePathForPackageReferenceStyleProject(project, BuildAssetsUtils.TargetsExtension)));

                    // Only include the dgspec if the user wants everything embedded in the binlog.
                    if (embedInBinlogSelection == 2)
                    {
                        LoggingQueue.Enqueue(new ConsoleOutLogEmbedInBinlog(Path.Combine(project.RestoreMetadata.OutputPath, DependencyGraphSpec.GetDGSpecFileName(Path.GetFileName(project.RestoreMetadata.ProjectPath)))));
                    }
                }
                else if (project.RestoreMetadata.ProjectStyle == ProjectStyle.PackagesConfig)
                {
                    string packagesConfigPath = BuildTasksUtility.GetPackagesConfigFilePath(project.RestoreMetadata.ProjectPath);

                    if (packagesConfigPath != null)
                    {
                        LoggingQueue.Enqueue(new ConsoleOutLogEmbedInBinlog(packagesConfigPath));
                    }
                }
            }
        }
    }
}