File: Utility\MSBuildProjectFrameworkUtility.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.Globalization;
using System.Linq;
using NuGet.Common;
using NuGet.Frameworks;

namespace NuGet.Commands
{
    public static class MSBuildProjectFrameworkUtility
    {
        /// <summary>
        /// Determine the target framework of an msbuild project.
        /// Returns the <see cref="FrameworkName"/> equivalent representation.
        /// </summary>
        /// <remarks>
        /// Do not use this when expecting a project to target a .NET 5 era framework.
        /// </remarks>
        public static IEnumerable<string> GetProjectFrameworkStrings(
            string projectFilePath,
            string targetFrameworks,
            string targetFramework,
            string targetFrameworkMoniker,
            string targetPlatformIdentifier,
            string targetPlatformVersion,
            string targetPlatformMinVersion)
        {
            return GetProjectFrameworkStrings(
                projectFilePath,
                targetFrameworks,
                targetFramework,
                targetFrameworkMoniker,
                targetPlatformIdentifier,
                targetPlatformVersion,
                targetPlatformMinVersion,
                isManagementPackProject: false,
                isXnaWindowsPhoneProject: false);
        }

        /// <summary>
        /// Given the properties from an msbuild project file and a the file path, infer the target framework.
        /// This method prioritizes projects without a framework, such as vcxproj and accounts for the mismatching arguments in UAP projects, where the TFI and TFV are set but should be ignored.
        /// Likewise, this method will *ignore* unnecessary properties, such as TPI and TPV when profiles are used, and frameworks that do not support platforms have some default values.
        /// </summary>
        /// <returns>The inferred framework. Unsupported otherwise.</returns>
        public static NuGetFramework GetProjectFramework(
            string projectFilePath,
            string targetFrameworkMoniker,
            string targetPlatformMoniker,
            string targetPlatformMinVersion)
        {
            return GetProjectFramework(
                projectFilePath,
                targetFrameworkMoniker,
                targetPlatformMoniker,
                targetPlatformIdentifier: null,
                targetPlatformVersion: null,
                targetPlatformMinVersion,
                clrSupport: null,
                windowsTargetPlatformMinVersion: null,
                isXnaWindowsPhoneProject: false,
                isManagementPackProject: false);
        }

        public static NuGetFramework GetProjectFramework(
            string projectFilePath,
            string targetFrameworkMoniker,
            string targetPlatformMoniker,
            string targetPlatformMinVersion,
            string clrSupport,
            string windowsTargetPlatformMinVersion)
        {
            return GetProjectFramework(
                projectFilePath,
                targetFrameworkMoniker,
                targetPlatformMoniker,
                targetPlatformIdentifier: null,
                targetPlatformVersion: null,
                targetPlatformMinVersion,
                clrSupport,
                windowsTargetPlatformMinVersion,
                isXnaWindowsPhoneProject: false,
                isManagementPackProject: false);
        }

        /// <summary>
        /// Determine the target framework of an msbuild project.
        /// Returns the <see cref="FrameworkName"/> equivalent representation.
        /// </summary>
        /// <remarks>
        /// Do not use this when expecting a project to target a .NET 5 era framework.
        /// </remarks>
        public static IEnumerable<string> GetProjectFrameworkStrings(
            string projectFilePath,
            string targetFrameworks,
            string targetFramework,
            string targetFrameworkMoniker,
            string targetPlatformIdentifier,
            string targetPlatformVersion,
            string targetPlatformMinVersion,
            bool isXnaWindowsPhoneProject,
            bool isManagementPackProject)
        {
            return GetProjectFrameworks(
                projectFilePath,
                targetFrameworks,
                targetFramework,
                targetFrameworkMoniker,
                targetPlatformMoniker: null,
                targetPlatformIdentifier,
                targetPlatformVersion,
                targetPlatformMinVersion,
                clrSupport: null,
                windowsTargetPlatformMinVersion: null,
                isXnaWindowsPhoneProject,
                isManagementPackProject);
        }

        internal static IEnumerable<string> GetProjectFrameworks(
            string projectFilePath,
            string targetFrameworks,
            string targetFramework,
            string targetFrameworkMoniker,
            string targetPlatformMoniker,
            string targetPlatformIdentifier,
            string targetPlatformVersion,
            string targetPlatformMinVersion,
            string clrSupport,
            string windowsTargetPlatformMinVersion,
            bool isXnaWindowsPhoneProject,
            bool isManagementPackProject)
        {
            var frameworks = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);

            // TargetFrameworks property
            frameworks.UnionWith(MSBuildStringUtility.Split(targetFrameworks));

            if (frameworks.Count > 0)
            {
                return frameworks;
            }

            // TargetFramework property
            var currentFrameworkString = MSBuildStringUtility.TrimAndGetNullForEmpty(targetFramework);

            if (!string.IsNullOrEmpty(currentFrameworkString))
            {
                frameworks.Add(currentFrameworkString);

                return frameworks;
            }

            return new string[] { GetProjectFramework(
                projectFilePath,
                targetFrameworkMoniker,
                targetPlatformMoniker,
                targetPlatformIdentifier,
                targetPlatformVersion,
                targetPlatformMinVersion,
                clrSupport,
                windowsTargetPlatformMinVersion,
                isXnaWindowsPhoneProject,
                isManagementPackProject).DotNetFrameworkName };
        }

        internal static NuGetFramework GetProjectFramework(
            string projectFilePath,
            string targetFrameworkMoniker,
            string targetPlatformMoniker,
            string targetPlatformIdentifier,
            string targetPlatformVersion,
            string targetPlatformMinVersion,
            string clrSupport,
            string windowsTargetPlatformMinVersion,
            bool isXnaWindowsPhoneProject,
            bool isManagementPackProject)
        {
            bool isCppCliSet = clrSupport?.Equals("NetCore", StringComparison.OrdinalIgnoreCase) == true;
            bool isCppCli = false;
            // C++ check
            if (projectFilePath?.EndsWith(".vcxproj", StringComparison.OrdinalIgnoreCase) == true)
            {
                if (!isCppCliSet)
                {
                    // The C++ project does not have a TargetFrameworkMoniker property set. 
                    // We hard-code the return value to Native.
                    return FrameworkConstants.CommonFrameworks.Native;
                }
                isCppCli = true;
            }

            // The MP project does not have a TargetFrameworkMoniker property set. 
            // We hard-code the return value to SCMPInfra.
            if (isManagementPackProject)
            {
                return NuGetFramework.Parse("SCMPInfra, Version=0.0");
            }

            // UAP/Windows store projects
            var platformMoniker = MSBuildStringUtility.TrimAndGetNullForEmpty(targetPlatformMoniker);
            var platformMonikerIdentifier = GetParts(platformMoniker).FirstOrDefault();

            var platformIdentifier = MSBuildStringUtility.TrimAndGetNullForEmpty(targetPlatformIdentifier);
            var platformMinVersion = MSBuildStringUtility.TrimAndGetNullForEmpty(targetPlatformMinVersion);
            var platformVersion = MSBuildStringUtility.TrimAndGetNullForEmpty(targetPlatformVersion);

            var effectivePlatformVersion = platformMinVersion ?? platformVersion;
            var effectivePlatformIdentifier = platformMonikerIdentifier ?? platformIdentifier;

            // Check for JS project
            if (projectFilePath?.EndsWith(".jsproj", StringComparison.OrdinalIgnoreCase) == true)
            {
                // JavaScript apps do not have a TargetFrameworkMoniker property set.
                // We read the TargetPlatformIdentifier and targetPlatformMinVersion instead
                // use the default values for JS if they were not given

                // Prefer moniker over individual properties
                if (!string.IsNullOrEmpty(platformMoniker))
                {
                    return GetFrameworkFromMoniker(platformMonikerIdentifier, platformMoniker, platformMinVersion);
                }

                if (string.IsNullOrEmpty(effectivePlatformVersion))
                {
                    effectivePlatformVersion = "0.0";
                }

                if (string.IsNullOrEmpty(effectivePlatformIdentifier))
                {
                    effectivePlatformIdentifier = FrameworkConstants.FrameworkIdentifiers.Windows;
                }

                return NuGetFramework.Parse($"{effectivePlatformIdentifier}, Version={effectivePlatformVersion}");
            }

            if (StringComparer.OrdinalIgnoreCase.Equals(effectivePlatformIdentifier, "UAP"))
            {
                // Use the platform id and versions, this is done for UAP projects
                // Prefer moniker over individual properties
                if (!string.IsNullOrEmpty(platformMoniker))
                {
                    return GetFrameworkFromMoniker(effectivePlatformIdentifier, platformMoniker, platformMinVersion);
                }
                if (!string.IsNullOrEmpty(effectivePlatformVersion))
                {
                    return NuGetFramework.Parse($"{effectivePlatformIdentifier}, Version={effectivePlatformVersion}");
                }
            }

            // TargetFrameworkMoniker
            var currentFrameworkString = MSBuildStringUtility.TrimAndGetNullForEmpty(targetFrameworkMoniker);

            if (!string.IsNullOrEmpty(currentFrameworkString))
            {
                // XNA project lies about its true identity, reporting itself as a normal .NET 4.0 project.
                // We detect it and changes its target framework to Silverlight4-WindowsPhone71
                if (isXnaWindowsPhoneProject
                    && ".NETFramework,Version=v4.0".Equals(currentFrameworkString, StringComparison.OrdinalIgnoreCase))
                {
                    currentFrameworkString = "Silverlight,Version=v4.0,Profile=WindowsPhone71";
                    return NuGetFramework.Parse(currentFrameworkString);
                }

                NuGetFramework framework = NuGetFramework.ParseComponents(currentFrameworkString, platformMoniker);

                if (isCppCli)
                {
                    if (!string.IsNullOrEmpty(windowsTargetPlatformMinVersion))
                    {
                        if (!Version.TryParse(windowsTargetPlatformMinVersion, out Version cppCliVersion))
                        {
                            throw new ArgumentException(string.Format(
                                CultureInfo.CurrentCulture,
                                Strings.Error_InvalidWindowsTargetPlatformMinVersion,
                                windowsTargetPlatformMinVersion));
                        }
                        framework = new NuGetFramework(framework.Framework, framework.Version, framework.Platform, cppCliVersion);
                    }

                    return new DualCompatibilityFramework(framework, FrameworkConstants.CommonFrameworks.Native);
                }

                return framework;
            }

            // Default to unsupported it no framework was found.
            return NuGetFramework.UnsupportedFramework;
        }

        private static NuGetFramework GetFrameworkFromMoniker(string platformIdentifier, string platformMoniker, string platformMinVersion)
        {
            if (!string.IsNullOrEmpty(platformMinVersion))
            {
                return NuGetFramework.Parse($"{platformIdentifier}, Version={platformMinVersion}");
            }
            else
            {
                return NuGetFramework.Parse(platformMoniker);
            }
        }

        private static string[] GetParts(string targetPlatformMoniker)
        {
            return targetPlatformMoniker != null ?
                targetPlatformMoniker.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray() :
                new string[] { };
        }


        /// <summary>
        /// Parse project framework strings into NuGetFrameworks.
        /// </summary>
        public static IEnumerable<NuGetFramework> GetProjectFrameworks(IEnumerable<string> frameworkStrings)
        {
            if (frameworkStrings == null)
            {
                throw new ArgumentNullException(nameof(frameworkStrings));
            }

            var frameworks = new List<NuGetFramework>();

            foreach (var frameworkString in frameworkStrings)
            {
                var parsed = NuGetFramework.Parse(frameworkString);

                // Replace if needed
                parsed = GetProjectFrameworkReplacement(parsed);

                // Add only unique frameworks
                if (!frameworks.Contains(parsed))
                {
                    frameworks.Add(parsed);
                }
            }

            return frameworks;
        }

        /// <summary>
        /// Parse existing nuget framework for .net core 4.5.1 or 4.5 and return compatible framework instance
        /// </summary>
        public static NuGetFramework GetProjectFrameworkReplacement(NuGetFramework framework)
        {
            if (framework == null)
            {
                throw new ArgumentNullException(nameof(framework));
            }

            // if the framework is .net core 4.5.1 return windows 8.1
            if (framework.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.NetCore, StringComparison.Ordinal)
                && framework.Version.Equals(Version.Parse("4.5.1.0")))
            {
                return new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Windows,
                       new Version("8.1"), framework.Profile);
            }
            // if the framework is .net core 4.5 return 8.0
            if (framework.Framework.Equals(FrameworkConstants.FrameworkIdentifiers.NetCore, StringComparison.Ordinal)
                && framework.Version.Equals(Version.Parse("4.5.0.0")))
            {
                return new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Windows,
                       new Version("8.0"), framework.Profile);
            }

            return framework;
        }
    }
}