File: Utilities\ToolLocationHelper_InstalledFrameworks.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.IO;
using Microsoft.Build.Shared;
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 frameworkName of the highest version of a framework given its root path and identifier.
    /// This is to optimize calls to GetHighestVersionOfTargetFramework
    /// </summary>
    private static Dictionary<string, FrameworkNameVersioning> s_cachedHighestFrameworkNameForTargetFrameworkIdentifier;

    /// <summary>
    /// Cache the list of supported frameworks
    /// </summary>
    private static List<string> s_targetFrameworkMonikers = null;

    /// <summary>
    /// Gets a IList of supported target framework monikers.
    /// </summary>
    /// <returns>list of supported target framework monikers</returns>
    public static IList<string> GetSupportedTargetFrameworks()
    {
        lock (s_locker)
        {
            if (s_targetFrameworkMonikers == null)
            {
                s_targetFrameworkMonikers = new List<string>();
                IList<string> frameworkIdentifiers = GetFrameworkIdentifiers(FrameworkLocationHelper.programFilesReferenceAssemblyLocation);
                foreach (string frameworkIdentifier in frameworkIdentifiers)
                {
                    IList<string> frameworkVersions = GetFrameworkVersions(FrameworkLocationHelper.programFilesReferenceAssemblyLocation, frameworkIdentifier);
                    foreach (string frameworkVersion in frameworkVersions)
                    {
                        Version version = VersionUtilities.ConvertToVersion(frameworkVersion);
                        s_targetFrameworkMonikers.Add(new FrameworkNameVersioning(frameworkIdentifier, version, null).FullName);

                        IList<string> frameworkProfile = GetFrameworkProfiles(FrameworkLocationHelper.programFilesReferenceAssemblyLocation, frameworkIdentifier, frameworkVersion);
                        foreach (string profile in frameworkProfile)
                        {
                            s_targetFrameworkMonikers.Add(new FrameworkNameVersioning(frameworkIdentifier, version, profile).FullName);
                        }
                    }
                }
            }
        }

        return s_targetFrameworkMonikers;
    }

    /// <summary>
    /// This method will return the highest version of a target framework moniker based on the identifier. This method will only
    /// find full frameworks, this means no profiles will be returned.
    /// </summary>
    public static FrameworkNameVersioning HighestVersionOfTargetFrameworkIdentifier(string targetFrameworkRootDirectory, string frameworkIdentifier)
    {
        ArgumentException.ThrowIfNullOrEmpty(targetFrameworkRootDirectory);
        ArgumentException.ThrowIfNullOrEmpty(frameworkIdentifier);

        string key = targetFrameworkRootDirectory + ";" + frameworkIdentifier;
        FrameworkNameVersioning highestFrameworkName = null;
        bool foundInCache = false;

        lock (s_locker)
        {
            if (s_cachedHighestFrameworkNameForTargetFrameworkIdentifier == null)
            {
                s_cachedHighestFrameworkNameForTargetFrameworkIdentifier = new Dictionary<string, FrameworkNameVersioning>(StringComparer.OrdinalIgnoreCase);
            }
            else
            {
                foundInCache = s_cachedHighestFrameworkNameForTargetFrameworkIdentifier.TryGetValue(key, out highestFrameworkName);
            }

            if (!foundInCache)
            {
                IList<string> frameworkVersions = GetFrameworkVersions(targetFrameworkRootDirectory, frameworkIdentifier);
                if (frameworkVersions.Count > 0)
                {
                    Version targetFrameworkVersion = ConvertTargetFrameworkVersionToVersion(frameworkVersions[frameworkVersions.Count - 1]);
                    highestFrameworkName = new FrameworkNameVersioning(frameworkIdentifier, targetFrameworkVersion);
                }

                s_cachedHighestFrameworkNameForTargetFrameworkIdentifier.Add(key, highestFrameworkName);
            }
        }

        return highestFrameworkName;
    }

    /// <summary>
    /// Given a string which may start with a "v" convert the string to a version object.
    /// </summary>
    private static Version ConvertTargetFrameworkVersionToVersion(string targetFrameworkVersion)
    {
        // Trim off the v if is is there.
        if (!string.IsNullOrEmpty(targetFrameworkVersion) && targetFrameworkVersion[0] is 'v' or 'V')
        {
#if NET
            return Version.Parse(targetFrameworkVersion.AsSpan(1));
#else
            return new Version(targetFrameworkVersion.Substring(1));
#endif
        }

        return new Version(targetFrameworkVersion);
    }

    /// <summary>
    /// Gets the installed framework identifiers
    /// </summary>
    /// <param name="frameworkReferenceRoot"></param>
    /// <returns></returns>
    internal static IList<string> GetFrameworkIdentifiers(string frameworkReferenceRoot)
    {
        if (string.IsNullOrEmpty(frameworkReferenceRoot))
        {
            throw new ArgumentException("Invalid frameworkReferenceRoot", nameof(frameworkReferenceRoot));
        }

        var frameworkIdentifiers = new List<string>();

        bool bAddDotNetFrameworkIdentifier = false;
        bool bFoundDotNetFrameworkIdentifier = false;
        bool programFilesReferenceAssemblyLocationFound = false;

        DirectoryInfo di = new DirectoryInfo(frameworkReferenceRoot);
        if (di.Exists)
        {
            if (frameworkReferenceRoot.Equals(FrameworkLocationHelper.programFilesReferenceAssemblyLocation, StringComparison.OrdinalIgnoreCase))
            {
                programFilesReferenceAssemblyLocationFound = true;
            }

            foreach (DirectoryInfo folder in di.GetDirectories())
            {
                if (programFilesReferenceAssemblyLocationFound &&
                    (
                        string.Equals(folder.Name, FrameworkLocationHelper.dotNetFrameworkVersionFolderPrefixV30, StringComparison.OrdinalIgnoreCase)
                        || string.Equals(folder.Name, FrameworkLocationHelper.dotNetFrameworkVersionFolderPrefixV35, StringComparison.OrdinalIgnoreCase)))
                {
                    bAddDotNetFrameworkIdentifier = true;
                    continue;
                }

                if (string.Equals(folder.Name, FrameworkLocationHelper.dotNetFrameworkIdentifier, StringComparison.OrdinalIgnoreCase))
                {
                    bFoundDotNetFrameworkIdentifier = true;
                }

                frameworkIdentifiers.Add(folder.Name);
            }
        }

        if (programFilesReferenceAssemblyLocationFound && !bFoundDotNetFrameworkIdentifier)
        {
            if (!bAddDotNetFrameworkIdentifier)
            {
                // special case for .NETFramework v2.0 - check also in the framework path because v20 does not have reference
                // assembly folders
                string dotNetFx20Path = GetPathToDotNetFramework(TargetDotNetFrameworkVersion.Version20);
                if (dotNetFx20Path != null)
                {
                    if (FileSystems.Default.DirectoryExists(dotNetFx20Path))
                    {
                        frameworkIdentifiers.Add(FrameworkLocationHelper.dotNetFrameworkIdentifier);
                    }
                }
            }
            else
            {
                frameworkIdentifiers.Add(FrameworkLocationHelper.dotNetFrameworkIdentifier);
            }
        }

        return frameworkIdentifiers;
    }

    /// <summary>
    /// Gets the installed versions for a given framework
    /// </summary>
    private static IList<string> GetFrameworkVersions(string frameworkReferenceRoot, string frameworkIdentifier)
    {
        if (string.IsNullOrEmpty(frameworkReferenceRoot))
        {
            throw new ArgumentException("Invalid frameworkReferenceRoot", nameof(frameworkReferenceRoot));
        }

        if (string.IsNullOrEmpty(frameworkIdentifier))
        {
            throw new ArgumentException("Invalid frameworkIdentifier", nameof(frameworkIdentifier));
        }

        var frameworkVersions = new List<string>();

        // backward compatibility with orcas
        // In case of orcas .NETFramework v3.0, v3.5 - the version folders are directly under the frameworkReferenceRoot
        // first check here
        if (string.Equals(frameworkIdentifier, FrameworkLocationHelper.dotNetFrameworkIdentifier, StringComparison.OrdinalIgnoreCase))
        {
            IList<string> versions = GetFx35AndEarlierVersions(frameworkReferenceRoot);
            if (versions.Count > 0)
            {
                frameworkVersions.AddRange(versions);
            }
        }

        // then look under the extensible multi-targeting layout - even for .NETFramework because future .NETFramework
        // versions would be at the right place
        string frameworkIdentifierPath = Path.Combine(frameworkReferenceRoot, frameworkIdentifier);

        DirectoryInfo dirInfoFxIdentifierPath = new DirectoryInfo(frameworkIdentifierPath);
        if (dirInfoFxIdentifierPath.Exists)
        {
            foreach (DirectoryInfo folder in dirInfoFxIdentifierPath.GetDirectories())
            {
                // the expected version folder name is of the format v<MajorVersion>.<MinorVersion> e.g. v3.5
                // only add if the version folder name is of the right format
                if (folder.Name.Length >= 4 && folder.Name.StartsWith("v", StringComparison.OrdinalIgnoreCase))
                {
                    if (Version.TryParse(
#if NET
                        folder.Name.AsSpan(1),
#else
                        folder.Name.Substring(1),
#endif
                        out _))
                    {
                        frameworkVersions.Add(folder.Name);
                    }
                }
            }
        }

        // sort in ascending order of the version numbers, this is important as later when we search for assemblies in other methods
        // we should be looking in ascending order of the framework version folders on disk
        frameworkVersions.Sort(VersionComparer.Instance);

        return frameworkVersions;
    }

    /// <summary>
    /// Get installed framework profiles
    /// </summary>
    /// <param name="frameworkReferenceRoot"></param>
    /// <param name="frameworkIdentifier"></param>
    /// <param name="frameworkVersion"></param>
    /// <returns></returns>
    private static IList<string> GetFrameworkProfiles(string frameworkReferenceRoot, string frameworkIdentifier, string frameworkVersion)
    {
        if (string.IsNullOrEmpty(frameworkReferenceRoot))
        {
            throw new ArgumentException("Invalid frameworkReferenceRoot", nameof(frameworkReferenceRoot));
        }

        if (string.IsNullOrEmpty(frameworkIdentifier))
        {
            throw new ArgumentException("Invalid frameworkIdentifier", nameof(frameworkIdentifier));
        }

        if (string.IsNullOrEmpty(frameworkVersion))
        {
            throw new ArgumentException("Invalid frameworkVersion", nameof(frameworkVersion));
        }

        var frameworkProfiles = new List<string>();

        string frameworkProfilePath = Path.Combine(frameworkReferenceRoot, frameworkIdentifier);
        frameworkProfilePath = Path.Combine(frameworkProfilePath, frameworkVersion);
        frameworkProfilePath = Path.Combine(frameworkProfilePath, "Profiles");

        DirectoryInfo dirInfoFxProfilePath = new DirectoryInfo(frameworkProfilePath);
        if (dirInfoFxProfilePath.Exists)
        {
            foreach (DirectoryInfo subType in dirInfoFxProfilePath.GetDirectories())
            {
                Version ver = VersionUtilities.ConvertToVersion(frameworkVersion);
                // check if profile is installed correctly
                IList<string> refAssemblyPaths = GetPathToReferenceAssemblies(new FrameworkNameVersioning(frameworkIdentifier, ver, subType.Name));
                if (refAssemblyPaths?.Count > 0)
                {
                    frameworkProfiles.Add(subType.Name);
                }
            }
        }

        return frameworkProfiles;
    }

    /// <summary>
    /// returns the .NETFramework versions lessthanOrEqualTo 3.5 installed in the machine
    /// Only returns Fx versions lessthanOrEqualTo 3.5 if DNFx3.5 is installed
    /// </summary>
    /// <param name="frameworkReferenceRoot"></param>
    /// <returns></returns>
    private static IList<string> GetFx35AndEarlierVersions(string frameworkReferenceRoot)
    {
        IList<string> versions = new List<string>();

        // only return v35 and earlier versions if .NetFx35 is installed
        string dotNetFx35Path = GetPathToDotNetFramework(TargetDotNetFrameworkVersion.Version35);

        if (dotNetFx35Path != null)
        {
            // .NetFx35 is installed

            // check v20
            string dotNetFx20Path = GetPathToDotNetFramework(TargetDotNetFrameworkVersion.Version20);
            if (dotNetFx20Path != null)
            {
                versions.Add("v2.0");
            }

            // check v30
            string dotNextFx30RefPath = Path.Combine(frameworkReferenceRoot, FrameworkLocationHelper.dotNetFrameworkVersionFolderPrefixV30);
            if (FileSystems.Default.DirectoryExists(dotNextFx30RefPath))
            {
                versions.Add(FrameworkLocationHelper.dotNetFrameworkVersionFolderPrefixV30);
            }

            // check v35
            string dotNextFx35RefPath = Path.Combine(frameworkReferenceRoot, FrameworkLocationHelper.dotNetFrameworkVersionFolderPrefixV35);
            if (FileSystems.Default.DirectoryExists(dotNextFx35RefPath))
            {
                versions.Add(FrameworkLocationHelper.dotNetFrameworkVersionFolderPrefixV35);
            }
        }

        return versions;
    }
}