File: Utilities\NuGetFrameworkWrapper.cs
Web Access
Project: src\msbuild\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// 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.Text;

namespace Microsoft.Build.Evaluation;

/// <summary>
/// Wraps the <c>NuGet.Frameworks</c> assembly for use during evaluation. On .NET (Core) the
/// assembly is referenced directly at compile time; on .NET Framework it is loaded by
/// reflection into a separate AppDomain for NGEN reasons (see
/// <see href="https://github.com/dotnet/msbuild/blob/main/documentation/NETFramework-NGEN.md"/>).
/// This partial holds the implementation-independent helpers shared by both paths.
/// </summary>
internal sealed partial class NuGetFrameworkWrapper
{
    /// <summary>
    /// Formats <paramref name="version"/> with at least <paramref name="minVersionPartCount"/>
    /// components, trimming any trailing zero parts beyond that minimum.
    /// </summary>
    private static string GetNonZeroVersionParts(Version version, int minVersionPartCount)
    {
        var nonZeroVersionParts = version.Revision == 0 ? version.Build == 0 ? version.Minor == 0 ? 1 : 2 : 3 : 4;
        return version.ToString(Math.Max(nonZeroVersionParts, minVersionPartCount));
    }

    /// <summary>
    /// Tiny adapter the shared <see cref="FilterTargetFrameworks{TParsed, TAdapter}"/> helper
    /// uses to read TFM properties without committing to a concrete NuGet.Frameworks type.
    /// The Core branch supplies a strongly-typed adapter over <c>NuGetFramework</c>; the
    /// .NET Framework branch supplies a reflection-based adapter over <c>object</c>.
    /// </summary>
    private interface ITfmAdapter<TParsed>
    {
        TParsed Parse(string tfm);
        string GetFramework(TParsed parsed);
        bool GetAllFrameworkVersions(TParsed parsed);
        Version GetVersion(TParsed parsed);
    }

    /// <summary>
    /// Filters <paramref name="incoming"/> down to the entries compatible with any entry in
    /// <paramref name="filter"/>, where "compatible" means same framework identifier (case
    /// insensitive) and either both sides match all versions or the parsed versions are equal.
    /// Constraining <typeparamref name="TAdapter"/> to a value type lets the JIT specialize and
    /// devirtualize the adapter calls, so the shared body has the same runtime profile as the
    /// inlined per-target implementation it replaced.
    /// </summary>
    private static string FilterTargetFrameworks<TParsed, TAdapter>(string incoming, string filter, TAdapter adapter)
        where TAdapter : struct, ITfmAdapter<TParsed>
    {
        IEnumerable<(string originalTfm, TParsed parsedTfm)> incomingFrameworks = ParseTfms(incoming, adapter);
        IEnumerable<(string originalTfm, TParsed parsedTfm)> filterFrameworks = [..ParseTfms(filter, adapter)];
        StringBuilder tfmList = new StringBuilder();

        // An incoming target framework from 'incoming' is kept if it is compatible with any of the desired target frameworks on 'filter'.
        foreach (var l in incomingFrameworks)
        {
            bool keep = false;
            foreach (var r in filterFrameworks)
            {
                if (adapter.GetFramework(l.parsedTfm).Equals(adapter.GetFramework(r.parsedTfm), StringComparison.OrdinalIgnoreCase) &&
                    ((adapter.GetAllFrameworkVersions(l.parsedTfm) && adapter.GetAllFrameworkVersions(r.parsedTfm)) ||
                     adapter.GetVersion(l.parsedTfm) == adapter.GetVersion(r.parsedTfm)))
                {
                    keep = true;
                    break;
                }
            }

            if (keep)
            {
                if (tfmList.Length > 0)
                {
                    tfmList.Append(';');
                }

                tfmList.Append(l.originalTfm);
            }
        }

        return tfmList.ToString();

        static IEnumerable<(string originalTfm, TParsed parsedTfm)> ParseTfms(string desiredTargetFrameworks, TAdapter adapter)
        {
            foreach (string tfm in desiredTargetFrameworks.Split([';'], StringSplitOptions.RemoveEmptyEntries))
            {
                yield return (tfm, adapter.Parse(tfm));
            }
        }
    }
}