File: Utilities\ToolLocationHelper_SDKFolders.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 Microsoft.Build.Framework;

#nullable disable

namespace Microsoft.Build.Utilities;

public static partial class ToolLocationHelper
{
    private const string retailConfigurationName = "Retail";
    private const string neutralArchitectureName = "Neutral";
    private const string commonConfigurationFolderName = "CommonConfiguration";
    private const string redistFolderName = "Redist";
    private const string referencesFolderName = "References";
    private const string designTimeFolderName = "DesignTime";

    /// <summary>
    /// Get the list of SDK folders which contains the references for the sdk at the sdkRoot provided
    /// in the order in which they should be searched for references.
    /// </summary>
    /// <param name="sdkRoot">Root folder for the SDK</param>
    /// <returns>A list of folders in the order which they should be used when looking for references in the SDK</returns>
    [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Shipped this way in Dev11 Beta (go-live)")]
    public static IList<string> GetSDKReferenceFolders(string sdkRoot)
        => GetSDKReferenceFolders(sdkRoot, retailConfigurationName, neutralArchitectureName);

    /// <summary>
    /// Get the list of SDK folders which contains the references for the sdk at the sdkRoot provided
    /// in the order in which they should be searched for references.
    /// </summary>
    /// <param name="sdkRoot">Root folder for the SDK</param>
    /// <param name="targetConfiguration">The configuration the SDK is targeting. This should be Debug or Retail</param>
    /// <param name="targetArchitecture">The architecture the SDK is targeting</param>
    /// <returns>A list of folders in the order which they should be used when looking for references in the SDK</returns>
    [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Shipped this way in Dev11 Beta (go-live)")]
    public static IList<string> GetSDKReferenceFolders(string sdkRoot, string targetConfiguration, string targetArchitecture)
    {
        ArgumentException.ThrowIfNullOrEmpty(sdkRoot);
        ArgumentException.ThrowIfNullOrEmpty(targetConfiguration);
        ArgumentException.ThrowIfNullOrEmpty(targetArchitecture);

        var referenceDirectories = new List<string>(4);

        string legacyWindowsMetadataLocation = Path.Combine(sdkRoot, "Windows Metadata");
        if (FileUtilities.DirectoryExistsNoThrow(legacyWindowsMetadataLocation))
        {
            legacyWindowsMetadataLocation = FileUtilities.EnsureTrailingSlash(legacyWindowsMetadataLocation);
            referenceDirectories.Add(legacyWindowsMetadataLocation);
        }

        AddSDKPaths(sdkRoot, referencesFolderName, targetConfiguration, targetArchitecture, referenceDirectories);

        return referenceDirectories;
    }

    /// <summary>
    /// Add the set of paths for where sdk files should be found. Where &lt;folderType&gt; is redist, references, designtime
    /// </summary>
    private static void AddSDKPaths(string sdkRoot, string folderName, string targetConfiguration, string targetArchitecture, List<string> directories)
    {
        targetArchitecture = RemapSdkArchitecture(targetArchitecture);

        // <SDKROOT>\<folderType>\Debug\X86
        AddSDKPath(sdkRoot, folderName, targetConfiguration, targetArchitecture, directories);

        if (!neutralArchitectureName.Equals(targetArchitecture, StringComparison.OrdinalIgnoreCase))
        {
            // <SDKROOT>\<folderType>\Debug\Neutral
            AddSDKPath(sdkRoot, folderName, targetConfiguration, neutralArchitectureName, directories);
        }

        // <SDKROOT>\<folderType>\CommonConfiguration\x86
        AddSDKPath(sdkRoot, folderName, commonConfigurationFolderName, targetArchitecture, directories);

        if (!neutralArchitectureName.Equals(targetArchitecture, StringComparison.OrdinalIgnoreCase))
        {
            // <SDKROOT>\<folderType>\CommonConfiguration\Neutral
            AddSDKPath(sdkRoot, folderName, commonConfigurationFolderName, neutralArchitectureName, directories);
        }
    }

    /// <summary>
    /// Get the list of SDK folders which contains the redist files for the sdk at the sdkRoot provided
    /// in the order in which they should be searched for references.
    /// </summary>
    /// <param name="sdkRoot">Root folder for the SDK must contain a redist folder</param>
    /// <returns>A list of folders in the order which they should be used when looking for redist files in the SDK</returns>
    [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Shipped this way in Dev11 Beta (go-live)")]
    public static IList<string> GetSDKRedistFolders(string sdkRoot)
        => GetSDKRedistFolders(sdkRoot, retailConfigurationName, neutralArchitectureName);

    /// <summary>
    /// Get the list of SDK folders which contains the redist files for the sdk at the sdkRoot provided
    /// in the order in which they should be searched for references.
    /// </summary>
    /// <param name="sdkRoot">Root folder for the SDK must contain a redist folder</param>
    /// <param name="targetConfiguration">The configuration the SDK is targeting. This should be Debug or Retail</param>
    /// <param name="targetArchitecture">The architecture the SDK is targeting</param>
    /// <returns>A list of folders in the order which they should be used when looking for redist files in the SDK</returns>
    [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Shipped this way in Dev11 Beta (go-live)")]
    public static IList<string> GetSDKRedistFolders(string sdkRoot, string targetConfiguration, string targetArchitecture)
    {
        ArgumentException.ThrowIfNullOrEmpty(sdkRoot);
        ArgumentException.ThrowIfNullOrEmpty(targetConfiguration);
        ArgumentException.ThrowIfNullOrEmpty(targetArchitecture);

        var redistDirectories = new List<string>(4);

        AddSDKPaths(sdkRoot, redistFolderName, targetConfiguration, targetArchitecture, redistDirectories);
        return redistDirectories;
    }

    /// <summary>
    /// Get the list of SDK folders which contains the designtime files for the sdk at the sdkRoot provided
    /// in the order in which they should be searched for references.
    /// </summary>
    /// <param name="sdkRoot">Root folder for the SDK must contain a Designtime folder</param>
    /// <returns>A list of folders in the order which they should be used when looking for DesignTime files in the SDK</returns>
    [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Shipped this way in Dev11 Beta (go-live)")]
    public static IList<string> GetSDKDesignTimeFolders(string sdkRoot)
        => GetSDKDesignTimeFolders(sdkRoot, retailConfigurationName, neutralArchitectureName);

    /// <summary>
    /// Get the list of SDK folders which contains the DesignTime files for the sdk at the sdkRoot provided
    /// in the order in which they should be searched for references.
    /// </summary>
    /// <param name="sdkRoot">Root folder for the SDK must contain a DesignTime folder</param>
    /// <param name="targetConfiguration">The configuration the SDK is targeting. This should be Debug or Retail</param>
    /// <param name="targetArchitecture">The architecture the SDK is targeting</param>
    /// <returns>A list of folders in the order which they should be used when looking for DesignTime files in the SDK</returns>
    [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SDK", Justification = "Shipped this way in Dev11 Beta (go-live)")]
    public static IList<string> GetSDKDesignTimeFolders(string sdkRoot, string targetConfiguration, string targetArchitecture)
    {
        ArgumentException.ThrowIfNullOrEmpty(sdkRoot);
        ArgumentException.ThrowIfNullOrEmpty(targetConfiguration);
        ArgumentException.ThrowIfNullOrEmpty(targetArchitecture);

        var designTimeDirectories = new List<string>(4);

        AddSDKPaths(sdkRoot, designTimeFolderName, targetConfiguration, targetArchitecture, designTimeDirectories);
        return designTimeDirectories;
    }
    /// <summary>
    /// Return the versioned/unversioned SDK content folder path
    /// </summary>
    /// <param name="sdkIdentifier">The identifier of the SDK</param>
    /// <param name="sdkVersion">The verision of the SDK</param>
    /// <param name="targetPlatformIdentifier">The identifier of 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="folderName">The content folder name under SDK path</param>
    /// <param name="diskRoot">An optional disk root to search.  A value should only be passed from a unit test.</param>
    /// <returns>The SDK content folder path</returns>
    public static string GetSDKContentFolderPath(
        string sdkIdentifier,
        string sdkVersion,
        string targetPlatformIdentifier,
        string targetPlatformMinVersion,
        string targetPlatformVersion,
        string folderName,
        string diskRoot = null)
    {
        ArgumentException.ThrowIfNullOrEmpty(sdkIdentifier);
        ArgumentException.ThrowIfNullOrEmpty(sdkVersion);

        // Avoid exception in Path.Combine
        folderName ??= string.Empty;

        // If no folder name is input or it isn't UWP SDK, return the root SDK path.
        if (string.IsNullOrWhiteSpace(folderName) || sdkVersion != "10.0" || !string.Equals(sdkIdentifier, "Windows", StringComparison.OrdinalIgnoreCase))
        {
            string sdkLocation = GetPlatformSDKLocation(sdkIdentifier, sdkVersion);
            return Path.Combine(sdkLocation, folderName);
        }

        ArgumentException.ThrowIfNullOrEmpty(targetPlatformIdentifier);
        ArgumentException.ThrowIfNullOrEmpty(targetPlatformVersion);

        string sdkContentFolderPath = null;

        TargetPlatformSDK matchingSdk = GetMatchingPlatformSDK(targetPlatformIdentifier, targetPlatformVersion, diskRoot, null, null);
        string platformKey = TargetPlatformSDK.GetSdkKey(targetPlatformIdentifier, targetPlatformVersion);
        if (TryGetPlatformManifest(matchingSdk, platformKey, out PlatformManifest manifest))
        {
            sdkContentFolderPath = manifest.VersionedContent
                ? Path.Combine(matchingSdk.Path, folderName, targetPlatformVersion)
                : Path.Combine(matchingSdk.Path, folderName);
        }

        return sdkContentFolderPath;
    }

    /// <summary>
    /// Remap some common architectures to a single one that will be in the SDK.
    /// </summary>
    private static string RemapSdkArchitecture(string targetArchitecture)
    {
        if (targetArchitecture.Equals("msil", StringComparison.OrdinalIgnoreCase) ||
           targetArchitecture.Equals("AnyCPU", StringComparison.OrdinalIgnoreCase) ||
           targetArchitecture.Equals("Any CPU", StringComparison.OrdinalIgnoreCase))
        {
            targetArchitecture = "Neutral";
        }
        else if (targetArchitecture.Equals("Amd64", StringComparison.OrdinalIgnoreCase))
        {
            targetArchitecture = "x64";
        }

        return targetArchitecture;
    }

    /// <summary>
    /// Add the reference folder to the list of reference directories if it exists.
    /// </summary>
    private static void AddSDKPath(string sdkRoot, string contentFolderName, string targetConfiguration, string targetArchitecture, List<string> contentDirectories)
    {
        string referenceAssemblyPath = Path.Combine(sdkRoot, contentFolderName, targetConfiguration, targetArchitecture);

        if (FileUtilities.DirectoryExistsNoThrow(referenceAssemblyPath))
        {
            referenceAssemblyPath = FileUtilities.EnsureTrailingSlash(referenceAssemblyPath);
            contentDirectories.Add(referenceAssemblyPath);
        }
    }
}