File: Utilities\ToolLocationHelper_PlatformSDKs.cs
Web Access
Project: Microsoft.Build.Framework.csproj (Microsoft.Build.Framework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared.FileSystem;
using FrameworkNameVersioning = System.Runtime.Versioning.FrameworkName;

#nullable disable

namespace Microsoft.Build.Utilities;

public static partial class ToolLocationHelper
{
    /// <summary>
    /// Cache the set of target platform references for a particular combination of inputs.  For legacy
    /// target platforms, this is just grabbing all winmds from the References\CommonConfiguration\Neutral
    /// folder; for OneCore-based platforms, this involves reading the list from Platform.xml and synthesizing
    /// the locations.
    /// </summary>
    private static Dictionary<string, string[]> s_cachedTargetPlatformReferences;
    /// <summary>
    /// Get a list target platform sdks on the machine.
    /// </summary>
    /// <returns>List of Target Platform SDKs, Item1: TargetPlatformName Item2: Version of SDK Item3: Path to sdk root</returns>
    public static IList<TargetPlatformSDK> GetTargetPlatformSdks()
        => GetTargetPlatformSdks(null, null);

    /// <summary>
    /// Get a list target platform sdks on the machine.
    /// </summary>
    /// <param name="diskRoots">List of disk locations to search for platform sdks</param>
    /// <param name="registryRoot">Registry root location to look for platform sdks</param>
    /// <returns>List of Target Platform SDKs</returns>
    public static IList<TargetPlatformSDK> GetTargetPlatformSdks(string[] diskRoots, string registryRoot)
    {
        IEnumerable<TargetPlatformSDK> targetPlatforms = RetrieveTargetPlatformList(diskRoots, null, registryRoot);
        return targetPlatforms.Where(platform => platform.Path != null).ToList();
    }

    /// <summary>
    /// Filter list of platform sdks based on minimum OS and VS versions
    /// </summary>
    /// <param name="targetPlatformSdkList">List of platform sdks</param>
    /// <param name="osVersion">Operating System version. Pass null to not filter based on this parameter</param>
    /// <param name="vsVersion">Visual Studio version. Pass null not to filter based on this parameter</param>
    /// <returns>List of Target Platform SDKs</returns>
    public static IList<TargetPlatformSDK> FilterTargetPlatformSdks(IList<TargetPlatformSDK> targetPlatformSdkList, Version osVersion, Version vsVersion)
    {
        var filteredTargetPlatformSdkList = new List<TargetPlatformSDK>();

        foreach (TargetPlatformSDK targetPlatformSdk in targetPlatformSdkList)
        {
            if ((targetPlatformSdk.MinOSVersion == null || osVersion == null || targetPlatformSdk.MinOSVersion <= osVersion) && // filter based on OS version - let pass if not in manifest or parameter
                (targetPlatformSdk.MinVSVersion == null || vsVersion == null || targetPlatformSdk.MinVSVersion <= vsVersion))    // filter based on VS version - let pass if not in manifest or parameter
            {
                filteredTargetPlatformSdkList.Add(targetPlatformSdk);
            }
        }

        return filteredTargetPlatformSdkList;
    }

    /// <summary>
    /// Get the location of the target platform SDK props file for a given {SDKI, SDKV, TPI, TPMinV, TPV} combination.
    /// </summary>
    /// <param name="sdkIdentifier">The OneCore SDK identifier that defines OnceCore SDK root</param>
    /// <param name="sdkVersion">The verision of the OneCore SDK</param>
    /// <param name="targetPlatformIdentifier">Identifier for the targeted platform</param>
    /// <param name="targetPlatformMinVersion">The min version of the targeted platform</param>
    /// <param name="targetPlatformVersion">The version of the targeted platform</param>
    /// <returns>Location of the target platform SDK props file without .props filename</returns>
    public static string GetPlatformSDKPropsFileLocation(string sdkIdentifier, string sdkVersion, string targetPlatformIdentifier, string targetPlatformMinVersion, string targetPlatformVersion)
        => GetPlatformSDKPropsFileLocation(sdkIdentifier, sdkVersion, targetPlatformIdentifier, targetPlatformMinVersion, targetPlatformVersion, null, null);

    /// <summary>
    /// Get the location of the target platform SDK props file for a given {SDKI, SDKV, TPI, TPMinV, TPV} combination.
    /// </summary>
    /// <param name="sdkIdentifier">The OneCore SDK identifier that defines OnceCore SDK root</param>
    /// <param name="sdkVersion">The verision of the OneCore SDK</param>
    /// <param name="targetPlatformIdentifier">Identifier for the targeted platform</param>
    /// <param name="targetPlatformMinVersion">The min version of the targeted platform</param>
    /// <param name="targetPlatformVersion">The version of the targeted platform</param>
    /// <param name="diskRoots">List of disk roots to search for sdks within</param>
    /// <param name="registryRoot">Registry root to look for sdks within</param>
    /// <returns>Location of the target platform SDK props file without .props filename</returns>
    public static string GetPlatformSDKPropsFileLocation(string sdkIdentifier, string sdkVersion, string targetPlatformIdentifier, string targetPlatformMinVersion, string targetPlatformVersion, string diskRoots, string registryRoot)
    {
        ArgumentException.ThrowIfNullOrEmpty(targetPlatformIdentifier);
        ArgumentException.ThrowIfNullOrEmpty(targetPlatformVersion);

        string propsFileLocation;
        try
        {
            // e.g. C:\Program Files (x86)\Windows Kits\8.2
            string sdkRoot = GetPlatformSDKLocation(targetPlatformIdentifier, targetPlatformVersion, diskRoots, registryRoot);

            if (!string.IsNullOrEmpty(sdkRoot))
            {
                // In the old SDK world, it is e.g. C:\Program Files (x86)\Windows Kits\8.2\DesignTime\CommonConfiguration\Neutral
                // In OneCore SDK world, it is e.g. C:\Program Files (x86)\Windows Kits\10.0\DesignTime\CommonConfiguration\Neutral\UAP\0.8.0.0
                if (string.IsNullOrEmpty(sdkIdentifier))
                {
                    propsFileLocation = Path.Combine(sdkRoot, designTimeFolderName, commonConfigurationFolderName, neutralArchitectureName);
                }
                else
                {
                    propsFileLocation = Path.Combine(sdkRoot, designTimeFolderName, commonConfigurationFolderName, neutralArchitectureName, targetPlatformIdentifier, targetPlatformVersion);
                }

                if (FileSystems.Default.DirectoryExists(propsFileLocation))
                {
                    return propsFileLocation;
                }
                else
                {
                    DebugTrace.WriteLine($"Target platform props file location '{propsFileLocation}' did not exist.");
                }
            }
            else
            {
                DebugTrace.WriteLine($"Could not find root SDK location for SDKI = '{sdkIdentifier}', SDKV = '{sdkVersion}'");
            }
        }
        catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
        {
            DebugTrace.WriteLine($"Encountered exception trying to get the SDK props file Location : {e.Message}");
        }

        return null;
    }

    /// <summary>
    /// Gathers the set of platform winmds for a particular {SDKI, SDKV, TPI, TPMinV, TPV} combination
    /// </summary>
    public static string[] GetTargetPlatformReferences(string sdkIdentifier, string sdkVersion, string targetPlatformIdentifier, string targetPlatformMinVersion, string targetPlatformVersion)
        => GetTargetPlatformReferences(sdkIdentifier, sdkVersion, targetPlatformIdentifier, targetPlatformMinVersion, targetPlatformVersion, null, null);

    /// <summary>
    /// Gathers the set of platform winmds for a particular {SDKI, SDKV, TPI, TPMinV, TPV} combination
    /// </summary>
    public static string[] GetTargetPlatformReferences(string sdkIdentifier, string sdkVersion, string targetPlatformIdentifier, string targetPlatformMinVersion, string targetPlatformVersion, string diskRoots, string registryRoot)
    {
        lock (s_locker)
        {
            s_cachedTargetPlatformReferences ??= new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);

            string cacheKey = string.Join("|", sdkIdentifier, sdkVersion, targetPlatformIdentifier, targetPlatformMinVersion, targetPlatformVersion, diskRoots, registryRoot);

            if (s_cachedTargetPlatformReferences.TryGetValue(cacheKey, out string[] targetPlatformReferences))
            {
                return targetPlatformReferences;
            }

            if (string.IsNullOrEmpty(sdkIdentifier) && string.IsNullOrEmpty(sdkVersion))
            {
                targetPlatformReferences = GetLegacyTargetPlatformReferences(targetPlatformIdentifier, targetPlatformVersion, diskRoots, registryRoot);
            }
            else
            {
                targetPlatformReferences = GetTargetPlatformReferencesFromManifest(sdkIdentifier, sdkVersion, targetPlatformIdentifier, targetPlatformMinVersion, targetPlatformVersion, diskRoots, registryRoot);
            }

            s_cachedTargetPlatformReferences.Add(cacheKey, targetPlatformReferences);
            return targetPlatformReferences;
        }
    }

    /// <summary>
    /// Gathers the set of platform winmds based on the assumption that they come from
    /// an SDK that is specified solely by TPI / TPV.
    /// </summary>
    private static string[] GetLegacyTargetPlatformReferences(string targetPlatformIdentifier, string targetPlatformVersion, string diskRoots, string registryRoot)
    {
        ArgumentException.ThrowIfNullOrEmpty(targetPlatformIdentifier);
        ArgumentException.ThrowIfNullOrEmpty(targetPlatformVersion);

        try
        {
            // TODO: Add caching so that we only have to read all this stuff in once.
            string sdkRoot = GetPlatformSDKLocation(targetPlatformIdentifier, targetPlatformVersion, diskRoots, registryRoot);
            string winmdLocation = null;

            if (!string.IsNullOrEmpty(sdkRoot))
            {
                winmdLocation = Path.Combine(sdkRoot, referencesFolderName, commonConfigurationFolderName, neutralArchitectureName);

                if (!FileSystems.Default.DirectoryExists(winmdLocation))
                {
                    DebugTrace.WriteLine($"Target platform location '{winmdLocation}' did not exist");
                    winmdLocation = null;
                }
            }
            else
            {
                DebugTrace.WriteLine($"Could not find root SDK location for TPI = '{targetPlatformIdentifier}', TPV = '{targetPlatformVersion}'");
            }

            if (!string.IsNullOrEmpty(winmdLocation))
            {
                string[] winmdPaths = Directory.GetFiles(winmdLocation, "*.winmd");

                if (winmdPaths.Length > 0)
                {
                    DebugTrace.WriteLine($"Found {winmdPaths.Length} contract winmds in '{winmdLocation}'");
                    return winmdPaths;
                }
            }
        }
        catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
        {
            DebugTrace.WriteLine($"Encountered exception trying to gather the platform references: {e.Message}");
        }

        return [];
    }

    /// <summary>
    /// Gathers the set of platform winmds for a particular {SDKI, SDKV, TPI, TPMinV, TPV} combination,
    /// based on the assumption that it is an SDK that has both {SDKI, SDKV} and TP* specifiers.
    /// </summary>
    private static string[] GetTargetPlatformReferencesFromManifest(
        string sdkIdentifier,
        string sdkVersion,
        string targetPlatformIdentifier,
        string targetPlatformMinVersion,
        string targetPlatformVersion,
        string diskRoots,
        string registryRoot)
    {
        ArgumentException.ThrowIfNullOrEmpty(sdkIdentifier);
        ArgumentException.ThrowIfNullOrEmpty(sdkVersion);
        ArgumentException.ThrowIfNullOrEmpty(targetPlatformIdentifier);
        ArgumentException.ThrowIfNullOrEmpty(targetPlatformVersion);

        string[] contractWinMDs = [];

        TargetPlatformSDK matchingSdk = GetMatchingPlatformSDK(targetPlatformIdentifier, targetPlatformVersion, diskRoots, null, registryRoot);
        string platformKey = TargetPlatformSDK.GetSdkKey(targetPlatformIdentifier, targetPlatformVersion);
        if (TryGetPlatformManifest(matchingSdk, platformKey, out PlatformManifest manifest))
        {
            if (manifest.VersionedContent)
            {
                contractWinMDs = GetApiContractReferences(manifest.ApiContracts, matchingSdk.Path, manifest.PlatformVersion);
            }
            else
            {
                contractWinMDs = GetApiContractReferences(manifest.ApiContracts, matchingSdk.Path);
            }
        }

        return contractWinMDs;
    }

    /// <summary>
    /// Return the WinMD paths referenced by the given api contracts and target sdk root
    /// </summary>
    /// <param name="apiContracts">The API contract definitions</param>
    /// <param name="targetPlatformSdkRoot">The root of the target platform SDK</param>
    /// <returns>List of matching WinMDs</returns>
    internal static string[] GetApiContractReferences(IEnumerable<ApiContract> apiContracts, string targetPlatformSdkRoot)
        => GetApiContractReferences(apiContracts, targetPlatformSdkRoot, string.Empty);

    /// <summary>
    /// Return the WinMD paths referenced by the given api contracts and target sdk root
    /// </summary>
    /// <param name="apiContracts">The API contract definitions</param>
    /// <param name="targetPlatformSdkRoot">The root of the target platform SDK</param>
    /// <param name="targetPlatformSdkVersion">The version of the target platform SDK</param>
    /// <returns>List of matching WinMDs</returns>
    internal static string[] GetApiContractReferences(IEnumerable<ApiContract> apiContracts, string targetPlatformSdkRoot, string targetPlatformSdkVersion)
    {
        if (apiContracts == null)
        {
            return [];
        }

        var contractWinMDs = new List<string>();

        string referencesRoot = Path.Combine(targetPlatformSdkRoot, referencesFolderName, targetPlatformSdkVersion);

        foreach (ApiContract contract in apiContracts)
        {
            DebugTrace.WriteLine($"Gathering contract references for contract with name '{contract.Name}' and version '{contract.Version}'");
            string contractPath = Path.Combine(referencesRoot, contract.Name, contract.Version);

            if (FileSystems.Default.DirectoryExists(contractPath))
            {
                string[] winmdPaths = Directory.GetFiles(contractPath, "*.winmd");

                if (winmdPaths.Length > 0)
                {
                    DebugTrace.WriteLine($"Found {winmdPaths.Length} contract winmds in '{contractPath}'");
                    contractWinMDs.AddRange(winmdPaths);
                }
            }
        }

        return contractWinMDs.ToArray();
    }

    private static bool TryGetPlatformManifest(TargetPlatformSDK matchingSdk, string platformKey, out PlatformManifest manifest)
    {
        manifest = null;
        try
        {
            string platformManifestLocation = null;

            if (matchingSdk != null)
            {
                if (!matchingSdk.Platforms.TryGetValue(platformKey, out platformManifestLocation))
                {
                    DebugTrace.WriteLine($"Target platform location '{platformManifestLocation}' did not exist or did not contain Platform.xml", category: "GetPlatformManifest");
                }
            }
            else
            {
                DebugTrace.WriteLine($"Could not find root SDK for '{platformKey}'", category: "GetPlatformManifest");
            }

            if (!string.IsNullOrEmpty(platformManifestLocation))
            {
                manifest = new PlatformManifest(platformManifestLocation);

                if (!manifest.ReadError)
                {
                    return true;
                }
            }
        }
        catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
        {
            DebugTrace.WriteLine($"Encountered exception trying to check if SDK is versioned: {e.Message}", category: "GetValueUsingMatchingSDKManifest");
        }

        return false;
    }

    /// <summary>
    /// Given a target platform identifier and a target platform version search the default sdk locations for the platform sdk for the target platform.
    /// </summary>
    /// <param name="targetPlatformIdentifier">Targeted platform to find SDKs for</param>
    /// <param name="targetPlatformVersion">Targeted platform version to find SDKs for</param>
    /// <returns>Location of the SDK if it is found, empty string if it could not be found</returns>
    [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Shipped this way in Dev11 Beta (go-live)")]
    public static string GetPlatformSDKLocation(string targetPlatformIdentifier, Version targetPlatformVersion)
        => GetPlatformSDKLocation(targetPlatformIdentifier, targetPlatformVersion, null, null);

    /// <summary>
    /// Given a target platform identifier and a target platform version search the default sdk locations for the platform sdk for the target platform.
    /// </summary>
    /// <param name="targetPlatformIdentifier">Targeted platform to find SDKs for</param>
    /// <param name="targetPlatformVersion">Targeted platform version to find SDKs for</param>
    /// <param name="diskRoots">List of disk roots to search for sdks within</param>
    /// <param name="registryRoot">Registry root to look for sdks within</param>
    /// <returns>Location of the SDK if it is found, empty string if it could not be found</returns>
    [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Shipped this way in Dev11 Beta (go-live)")]
    public static string GetPlatformSDKLocation(string targetPlatformIdentifier, Version targetPlatformVersion, string[] diskRoots, string registryRoot)
    {
        var targetPlatform = GetMatchingPlatformSDK(targetPlatformIdentifier, targetPlatformVersion, diskRoots, null, registryRoot);
        return targetPlatform?.Path ?? string.Empty;
    }

    /// <summary>
    /// Given a target platform identifier and a target platform version search the default sdk locations for the platform sdk for the target platform.
    /// </summary>
    /// <param name="targetPlatformIdentifier">Identifier for the platform</param>
    /// <param name="targetPlatformVersion">Version of the platform</param>
    /// <returns>A full path to the sdk root if the sdk exists in the targeted platform or an empty string if it does not exist.</returns>
    [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Shipped this way in Dev11 Beta (go-live)")]
    public static string GetPlatformSDKLocation(string targetPlatformIdentifier, string targetPlatformVersion)
        => GetPlatformSDKLocation(targetPlatformIdentifier, targetPlatformVersion, null, null);

    /// <summary>
    /// Given a target platform identifier and a target platform version search the default sdk locations for the platform sdk for the target platform.
    /// </summary>
    /// <param name="targetPlatformIdentifier">Targeted platform to find SDKs for</param>
    /// <param name="targetPlatformVersion">Targeted platform version to find SDKs for</param>
    /// <param name="diskRoots">List of disk roots to search for sdks within</param>
    /// <param name="registryRoot">Registry root to look for sdks within</param>
    /// <returns>Location of the platform SDK if it is found, empty string if it could not be found</returns>
    [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Shipped this way in Dev11 Beta (go-live)")]
    public static string GetPlatformSDKLocation(string targetPlatformIdentifier, string targetPlatformVersion, string diskRoots, string registryRoot)
    {
        ArgumentNullException.ThrowIfNull(targetPlatformVersion);

        string[] sdkDiskRoots = null;
        if (!string.IsNullOrEmpty(diskRoots))
        {
            sdkDiskRoots = diskRoots.Split(s_diskRootSplitChars, StringSplitOptions.RemoveEmptyEntries);
        }

        string sdkLocation = string.Empty;
        if (Version.TryParse(targetPlatformVersion, out Version platformVersion))
        {
            sdkLocation = GetPlatformSDKLocation(targetPlatformIdentifier, platformVersion, sdkDiskRoots, registryRoot);
        }

        return sdkLocation;
    }

    /// <summary>
    /// Given a target platform identifier and version, get the display name for that platform SDK.
    /// </summary>
    public static string GetPlatformSDKDisplayName(string targetPlatformIdentifier, string targetPlatformVersion)
        => GetPlatformSDKDisplayName(targetPlatformIdentifier, targetPlatformVersion, null, null);

    /// <summary>
    /// Given a target platform identifier and version, get the display name for that platform SDK.
    /// </summary>
    public static string GetPlatformSDKDisplayName(string targetPlatformIdentifier, string targetPlatformVersion, string diskRoots, string registryRoot)
    {
        TargetPlatformSDK targetPlatform = GetMatchingPlatformSDK(targetPlatformIdentifier, targetPlatformVersion, diskRoots, null, registryRoot);
        return targetPlatform?.DisplayName ?? GenerateDefaultSDKDisplayName(targetPlatformIdentifier, targetPlatformVersion);
    }

    /// <summary>
    /// Given an SDK identifier and an SDK version, return a list of installed platforms.
    /// </summary>
    /// <param name="sdkIdentifier">SDK for which to find the installed platforms</param>
    /// <param name="sdkVersion">SDK version for which to find the installed platforms</param>
    /// <returns>A list of keys for the installed platforms for the given SDK</returns>
    public static IEnumerable<string> GetPlatformsForSDK(string sdkIdentifier, Version sdkVersion)
        => GetPlatformsForSDK(sdkIdentifier, sdkVersion, null, null);

    /// <summary>
    /// Given an SDK identifier and an SDK version, return a list of installed platforms.
    /// </summary>
    /// <param name="sdkIdentifier">SDK for which to find the installed platforms</param>
    /// <param name="sdkVersion">SDK version for which to find the installed platforms</param>
    /// <param name="diskRoots">List of disk roots to search for sdks within</param>
    /// <param name="registryRoot">Registry root to look for sdks within</param>
    /// <returns>A list of keys for the installed platforms for the given SDK</returns>
    public static IEnumerable<string> GetPlatformsForSDK(string sdkIdentifier, Version sdkVersion, string[] diskRoots, string registryRoot)
    {
        ArgumentNullException.ThrowIfNull(sdkIdentifier);
        ArgumentNullException.ThrowIfNull(sdkVersion);

        IEnumerable<TargetPlatformSDK> targetPlatformSDKs = RetrieveTargetPlatformList(diskRoots, null, registryRoot);

        var platforms = new List<string>();
        foreach (TargetPlatformSDK sdk in targetPlatformSDKs)
        {
            bool isSDKMatch = string.Equals(sdk.TargetPlatformIdentifier, sdkIdentifier, StringComparison.OrdinalIgnoreCase) && Equals(sdk.TargetPlatformVersion, sdkVersion);
            if (!isSDKMatch || sdk.Platforms == null)
            {
                continue;
            }

            foreach (string platform in sdk.Platforms.Keys)
            {
                platforms.Add(platform);
            }
        }

        return platforms;
    }

    /// <summary>
    /// Given an SDK Identifier and SDK version, return the latest installed platform.
    /// </summary>
    /// <param name="sdkIdentifier">SDK for which to find the latest installed platform</param>
    /// <param name="sdkVersion">SDK version for which to find the latest installed platform</param>
    /// <returns>The latest installed version for the given SDK</returns>
    public static string GetLatestSDKTargetPlatformVersion(string sdkIdentifier, string sdkVersion)
        => GetLatestSDKTargetPlatformVersion(sdkIdentifier, sdkVersion, null);

    /// <summary>
    /// Given an SDK Identifier and SDK version, return the latest installed platform.
    /// </summary>
    /// <param name="sdkIdentifier">SDK for which to find the latest installed platform</param>
    /// <param name="sdkVersion">SDK version for which to find the latest installed platform</param>
    /// <param name="sdkRoots">SDK Root folders</param>
    /// <returns>The latest installed version for the given SDK</returns>
    public static string GetLatestSDKTargetPlatformVersion(string sdkIdentifier, string sdkVersion, string[] sdkRoots)
    {
        ArgumentNullException.ThrowIfNull(sdkIdentifier);
        ArgumentNullException.ThrowIfNull(sdkVersion);

        var availablePlatformVersions = new List<Version>();
        IEnumerable<string> platformMonikerList = GetPlatformsForSDK(sdkIdentifier, new Version(sdkVersion), sdkRoots, null);

        Version platformVersion;
        foreach (string platformMoniker in platformMonikerList)
        {
            if (TryParsePlatformVersion(platformMoniker, out platformVersion))
            {
                availablePlatformVersions.Add(platformVersion);
            }
        }

        if (availablePlatformVersions?.Count > 0)
        {
            return availablePlatformVersions.OrderByDescending(x => x).FirstOrDefault().ToString();
        }
        else
        {
            return string.Empty;
        }
    }

    /// <summary>
    /// Tries to parse the "version" out of a platformMoniker.
    /// </summary>
    /// <param name="platformMoniker">PlatformMoniker, in the form "PlatformName, Version=version"</param>
    /// <param name="platformVersion">The version of the platform, if the parse was successful - Else set to null</param>
    /// <returns>True if parse was successful, false otherwise </returns>
    private static bool TryParsePlatformVersion(string platformMoniker, out Version platformVersion)
    {
        platformVersion = null;
        FrameworkNameVersioning framework = null;
        try
        {
            framework = new FrameworkNameVersioning(platformMoniker);
        }
        catch (ArgumentException e)
        {
            DebugTrace.WriteLine($"Cannot create FrameworkName object, Exception:{e.Message}");
        }

        if (framework != null)
        {
            platformVersion = framework.Version;
            return true;
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    /// Given a target platform identifier and version and locations in which to search, find the TargetPlatformSDK
    /// object that matches.
    /// </summary>
    private static TargetPlatformSDK GetMatchingPlatformSDK(string targetPlatformIdentifier, string targetPlatformVersion, string diskRoots, string multiPlatformDiskRoots, string registryRoot)
    {
        ArgumentNullException.ThrowIfNull(targetPlatformVersion);

        string[] sdkDiskRoots = null;
        if (!string.IsNullOrEmpty(diskRoots))
        {
            sdkDiskRoots = diskRoots.Split(s_diskRootSplitChars, StringSplitOptions.RemoveEmptyEntries);
        }

        string[] sdkmultiPlatformDiskRoots = null;
        if (!string.IsNullOrEmpty(multiPlatformDiskRoots))
        {
            sdkmultiPlatformDiskRoots = multiPlatformDiskRoots.Split(s_diskRootSplitChars, StringSplitOptions.RemoveEmptyEntries);
        }

        if (Version.TryParse(targetPlatformVersion, out Version platformVersion))
        {
            return GetMatchingPlatformSDK(targetPlatformIdentifier, platformVersion, sdkDiskRoots, sdkmultiPlatformDiskRoots, registryRoot);
        }

        return null;
    }

    /// <summary>
    /// Given a target platform identifier and version and locations in which to search, find the TargetPlatformSDK
    /// object that matches.
    /// </summary>
    private static TargetPlatformSDK GetMatchingPlatformSDK(string targetPlatformIdentifier, Version targetPlatformVersion, string[] diskRoots, string[] multiPlatformDiskRoots, string registryRoot)
    {
        ArgumentException.ThrowIfNullOrEmpty(targetPlatformIdentifier);
        ArgumentNullException.ThrowIfNull(targetPlatformVersion);

        IEnumerable<TargetPlatformSDK> targetPlatforms = RetrieveTargetPlatformList(diskRoots, multiPlatformDiskRoots, registryRoot);

        TargetPlatformSDK matchingSdk = targetPlatforms
            .FirstOrDefault(platform =>
                string.Equals(platform.TargetPlatformIdentifier, targetPlatformIdentifier, StringComparison.OrdinalIgnoreCase)
                && Equals(platform.TargetPlatformVersion, targetPlatformVersion));

        // For UAP platforms match against registered platforms...
        // Logic is same as used for managed UAP projects
        // vsproject\flavors\ProjectFlavoring\Microsoft.VisualStudio.ProjectFlavoring\Microsoft\VisualStudio\ProjectFlavoring\Retargeting\Management\VsMultiTargetingPlatformProvider.cs:FindPlatformSdk
        if (matchingSdk == null)
        {
            string versionString = targetPlatformVersion.ToString();
            matchingSdk = targetPlatforms.FirstOrDefault(platform => platform.ContainsPlatform(targetPlatformIdentifier, versionString));
        }

        return matchingSdk;
    }

    /// <summary>
    /// Given a target platform identifier and version, generate a reasonable default display name.
    /// </summary>
    /// <param name="targetPlatformIdentifier"></param>
    /// <param name="targetPlatformVersion"></param>
    private static string GenerateDefaultSDKDisplayName(string targetPlatformIdentifier, string targetPlatformVersion)
        => targetPlatformIdentifier + " " + targetPlatformVersion;
}