File: Validators\BaselinePackageValidator.cs
Web Access
Project: ..\..\..\src\Compatibility\ApiCompat\Microsoft.DotNet.PackageValidation\Microsoft.DotNet.PackageValidation.csproj (Microsoft.DotNet.PackageValidation)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.DotNet.ApiCompatibility.Logging;
using Microsoft.DotNet.ApiCompatibility.Runner;
using NuGet.ContentModel;
using NuGet.Frameworks;
 
namespace Microsoft.DotNet.PackageValidation.Validators
{
    /// <summary>
    /// Validates that no target framework / rid support is dropped in the latest package.
    /// Reports all the breaking changes in the latest package.
    /// </summary>
    public sealed class BaselinePackageValidator(ISuppressibleLog log,
        IApiCompatRunner apiCompatRunner) : IPackageValidator
    {
        /// <summary>
        /// Validates the latest NuGet package doesn't drop any target framework/rid and does not introduce any breaking changes.
        /// </summary>
        /// <param name="options"><see cref="PackageValidatorOption"/> to configure the baseline package validation.</param>
        public void Validate(PackageValidatorOption options)
        {
            if (options.BaselinePackage is null)
            {
                throw new ArgumentNullException(nameof(options.BaselinePackage));
            }
 
            ApiCompatRunnerOptions apiCompatOptions = new(options.EnableStrictMode, isBaselineComparison: true);
 
            foreach (NuGetFramework baselineTargetFramework in options.BaselinePackage.FrameworksInPackage)
            {
                // Skip target frameworks excluded from the baseline package.
                if (options.BaselinePackageFrameworkFilter is not null &&
                   options.BaselinePackageFrameworkFilter.IsExcluded(baselineTargetFramework))
                {
                    continue;
                }
 
                // Retrieve the compile time assets from the baseline package
                IReadOnlyList<ContentItem>? baselineCompileAssets = options.BaselinePackage.FindBestCompileAssetForFramework(baselineTargetFramework);
                if (baselineCompileAssets != null)
                {
                    // Search for compatible compile time assets in the latest package.
                    IReadOnlyList<ContentItem>? latestCompileAssets = options.Package.FindBestCompileAssetForFramework(baselineTargetFramework);
                    if (latestCompileAssets == null)
                    {
                        log.LogError(new Suppression(DiagnosticIds.TargetFrameworkDropped) { Target = baselineTargetFramework.ToString() },
                            DiagnosticIds.TargetFrameworkDropped,
                            string.Format(Resources.MissingTargetFramework,
                                baselineTargetFramework));
                    }
                    else if (options.EnqueueApiCompatWorkItems)
                    {
                        apiCompatRunner.QueueApiCompatFromContentItem(log,
                            baselineCompileAssets,
                            latestCompileAssets,
                            apiCompatOptions,
                            options.BaselinePackage,
                            options.Package);
                    }
                }
 
                // Retrieve runtime baseline assets and searches for compatible runtime assets in the latest package.
                IReadOnlyList<ContentItem>? baselineRuntimeAssets = options.BaselinePackage.FindBestRuntimeAssetForFramework(baselineTargetFramework);
                if (baselineRuntimeAssets != null)
                {
                    // Search for compatible runtime assets in the latest package.
                    IReadOnlyList<ContentItem>? latestRuntimeAssets = options.Package.FindBestRuntimeAssetForFramework(baselineTargetFramework);
                    if (latestRuntimeAssets == null)
                    {
                        log.LogError(new Suppression(DiagnosticIds.TargetFrameworkDropped) { Target = baselineTargetFramework.ToString() },
                            DiagnosticIds.TargetFrameworkDropped,
                            string.Format(Resources.MissingTargetFramework,
                                baselineTargetFramework));
                    }
                    else if (options.EnqueueApiCompatWorkItems)
                    {
                        apiCompatRunner.QueueApiCompatFromContentItem(log,
                            baselineRuntimeAssets,
                            latestRuntimeAssets,
                            apiCompatOptions,
                            options.BaselinePackage,
                            options.Package);
                    }
                }
 
                // Retrieve runtime specific baseline assets and searches for compatible runtime specific assets in the latest package.
                IReadOnlyList<ContentItem>? baselineRuntimeSpecificAssets = options.BaselinePackage.FindBestRuntimeSpecificAssetForFramework(baselineTargetFramework);
                if (baselineRuntimeSpecificAssets != null && baselineRuntimeSpecificAssets.Count > 0)
                {
                    IEnumerable<IGrouping<string, ContentItem>> baselineRuntimeSpecificAssetsRidGroupedPerRid = baselineRuntimeSpecificAssets
                        .Where(t => t.Path.StartsWith("runtimes"))
                        .GroupBy(t => (string)t.Properties["rid"]);
 
                    foreach (IGrouping<string, ContentItem> baselineRuntimeSpecificAssetsRidGroup in baselineRuntimeSpecificAssetsRidGroupedPerRid)
                    {
                        IReadOnlyList<ContentItem>? latestRuntimeSpecificAssets = options.Package.FindBestRuntimeAssetForFrameworkAndRuntime(baselineTargetFramework, baselineRuntimeSpecificAssetsRidGroup.Key);
                        if (latestRuntimeSpecificAssets == null)
                        {
                            log.LogError(new Suppression(DiagnosticIds.TargetFrameworkAndRidPairDropped) { Target = baselineTargetFramework.ToString() + "-" + baselineRuntimeSpecificAssetsRidGroup.Key },
                                DiagnosticIds.TargetFrameworkAndRidPairDropped,
                                string.Format(Resources.MissingTargetFrameworkAndRid,
                                    baselineTargetFramework,
                                    baselineRuntimeSpecificAssetsRidGroup.Key));
                        }
                        else if (options.EnqueueApiCompatWorkItems)
                        {
                            apiCompatRunner.QueueApiCompatFromContentItem(log,
                                baselineRuntimeSpecificAssetsRidGroup.ToArray(),
                                latestRuntimeSpecificAssets,
                                apiCompatOptions,
                                options.BaselinePackage,
                                options.Package);
                        }
                    }
                }
            }
 
            // If baseline target frameworks are excluded, provide additional logging.
            if (options.BaselinePackageFrameworkFilter is not null &&
                options.BaselinePackageFrameworkFilter.FoundExcludedTargetFrameworks.Count > 0)
            {
                log.LogMessage(ApiSymbolExtensions.Logging.MessageImportance.Normal,
                    string.Format(Resources.BaselineTargetFrameworksIgnored,
                        string.Join(", ", options.BaselinePackageFrameworkFilter.FoundExcludedTargetFrameworks)));
 
                // If a baseline target framework is ignored but present in the current package, emit a warning.
                // This should help avoiding unintentional exclusions when using wildcards in the exclusion patterns.
                string[] baselineTargetFrameworksExcludedButPresentInCurrentPackage = options.Package.FrameworksInPackage
                    .Select(framework => framework.GetShortFolderName())
                    .Intersect(options.BaselinePackageFrameworkFilter.FoundExcludedTargetFrameworks)
                    .ToArray();
                foreach (string baselineTargetFrameworkExcludedButPresentInCurrentPackage in baselineTargetFrameworksExcludedButPresentInCurrentPackage)
                {
                    log.LogWarning(new Suppression(DiagnosticIds.BaselineTargetFrameworkIgnoredButPresentInCurrentPackage,
                        baselineTargetFrameworkExcludedButPresentInCurrentPackage),
                        DiagnosticIds.BaselineTargetFrameworkIgnoredButPresentInCurrentPackage,
                        string.Format(Resources.BaselineTargetFrameworkIgnoredButPresentInCurrentPackage,
                            baselineTargetFrameworkExcludedButPresentInCurrentPackage));
                }
            }
 
            if (options.ExecuteApiCompatWorkItems)
            {
                apiCompatRunner.ExecuteWorkItems();
            }
        }
    }
}