diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ValidatePackage.cs b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ValidatePackage.cs index 788943051653..53f397fc840f 100644 --- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ValidatePackage.cs +++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ValidatePackage.cs @@ -30,7 +30,8 @@ public static void Run(Func logFactory, string? baselinePackagePath, string? runtimeGraph, IReadOnlyDictionary>? packageAssemblyReferences, - IReadOnlyDictionary>? baselinePackageAssemblyReferences) + IReadOnlyDictionary>? baselinePackageAssemblyReferences, + string[]? baselinePackageFrameworksToIgnore) { // Initialize the service provider ApiCompatServiceProvider serviceProvider = new(logFactory, @@ -70,7 +71,8 @@ public static void Run(Func logFactory, enableStrictMode: enableStrictModeForBaselineValidation, enqueueApiCompatWorkItems: runApiCompat, executeApiCompatWorkItems: false, - baselinePackage: Package.Create(baselinePackagePath, baselinePackageAssemblyReferences))); + Package.Create(baselinePackagePath, baselinePackageAssemblyReferences), + baselinePackageFrameworksToIgnore)); } if (runApiCompat) diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Task/ValidatePackageTask.cs b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Task/ValidatePackageTask.cs index 8fe35a179cf7..7c71e7a28d63 100644 --- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Task/ValidatePackageTask.cs +++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Task/ValidatePackageTask.cs @@ -118,6 +118,12 @@ public class ValidatePackageTask : TaskBase /// public ITaskItem[]? BaselinePackageAssemblyReferences { get; set; } + /// + /// A set of target frameworks to ignore from the baseline package. + /// The framework string must exactly match the folder name in the baseilne package. + /// + public string[]? BaselinePackageFrameworksToIgnore { get; set; } + public override bool Execute() { RoslynResolver roslynResolver = RoslynResolver.Register(RoslynAssembliesPath!); @@ -153,15 +159,16 @@ protected override void ExecuteCore() BaselinePackageTargetPath, RuntimeGraph, ParsePackageAssemblyReferences(PackageAssemblyReferences), - ParsePackageAssemblyReferences(BaselinePackageAssemblyReferences)); + ParsePackageAssemblyReferences(BaselinePackageAssemblyReferences), + BaselinePackageFrameworksToIgnore); } private static Dictionary>? ParsePackageAssemblyReferences(ITaskItem[]? packageAssemblyReferences) { - if (packageAssemblyReferences == null || packageAssemblyReferences.Length == 0) + if (packageAssemblyReferences is null || packageAssemblyReferences.Length == 0) return null; - Dictionary>? packageAssemblyReferencesDict = new(packageAssemblyReferences.Length); + Dictionary> packageAssemblyReferencesDict = new(packageAssemblyReferences.Length); foreach (ITaskItem taskItem in packageAssemblyReferences) { string targetFrameworkMoniker = taskItem.GetMetadata("TargetFrameworkMoniker"); diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Tool/Program.cs b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Tool/Program.cs index b6d0140e9f50..1474f687718f 100644 --- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Tool/Program.cs +++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompat.Tool/Program.cs @@ -270,6 +270,12 @@ static int Main(string[] args) Arity = ArgumentArity.ZeroOrMore, HelpName = "tfm=file1,file2,..." }; + CliOption baselinePackageFrameworksToIgnoreOption = new("--baseline-package-frameworks-to-ignore") + { + Description = "A set of target frameworks to ignore from the baseline package. The framework string must exactly match the folder name in the baseline package.", + AllowMultipleArgumentsPerToken = true, + Arity = ArgumentArity.ZeroOrMore, + }; CliCommand packageCommand = new("package", "Validates the compatibility of package assets"); packageCommand.Arguments.Add(packageArgument); @@ -281,6 +287,7 @@ static int Main(string[] args) packageCommand.Options.Add(baselinePackageOption); packageCommand.Options.Add(packageAssemblyReferencesOption); packageCommand.Options.Add(baselinePackageAssemblyReferencesOption); + packageCommand.Options.Add(baselinePackageFrameworksToIgnoreOption); packageCommand.SetAction((ParseResult parseResult) => { // If a roslyn assemblies path isn't provided, use the compiled against version from a subfolder. @@ -309,6 +316,7 @@ static int Main(string[] args) string? runtimeGraph = parseResult.GetValue(runtimeGraphOption); Dictionary>? packageAssemblyReferences = parseResult.GetValue(packageAssemblyReferencesOption); Dictionary>? baselinePackageAssemblyReferences = parseResult.GetValue(baselinePackageAssemblyReferencesOption); + string[]? baselinePackageFrameworksToIgnore = parseResult.GetValue(baselinePackageFrameworksToIgnoreOption); SuppressibleConsoleLog logFactory(ISuppressionEngine suppressionEngine) => new(suppressionEngine, verbosity); ValidatePackage.Run(logFactory, @@ -330,7 +338,8 @@ static int Main(string[] args) baselinePackage, runtimeGraph, packageAssemblyReferences, - baselinePackageAssemblyReferences); + baselinePackageAssemblyReferences, + baselinePackageFrameworksToIgnore); roslynResolver.Unregister(); }); diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.PackageValidation/Validators/BaselinePackageValidator.cs b/src/Compatibility/ApiCompat/Microsoft.DotNet.PackageValidation/Validators/BaselinePackageValidator.cs index e599ad805bb3..6f3e113862f6 100644 --- a/src/Compatibility/ApiCompat/Microsoft.DotNet.PackageValidation/Validators/BaselinePackageValidator.cs +++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.PackageValidation/Validators/BaselinePackageValidator.cs @@ -28,9 +28,15 @@ public void Validate(PackageValidatorOption options) ApiCompatRunnerOptions apiCompatOptions = new(options.EnableStrictMode, isBaselineComparison: true); - // Iterate over all target frameworks in the package. foreach (NuGetFramework baselineTargetFramework in options.BaselinePackage.FrameworksInPackage) { + // Skip target frameworks excluded from the baseline package. + if (options.BaselinePackageFrameworksToIgnore is not null && + options.BaselinePackageFrameworksToIgnore.Contains(baselineTargetFramework.GetShortFolderName())) + { + continue; + } + // Retrieve the compile time assets from the baseline package IReadOnlyList? baselineCompileAssets = options.BaselinePackage.FindBestCompileAssetForFramework(baselineTargetFramework); if (baselineCompileAssets != null) diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.PackageValidation/Validators/PackageValidatorOption.cs b/src/Compatibility/ApiCompat/Microsoft.DotNet.PackageValidation/Validators/PackageValidatorOption.cs index d1224551e13a..0ccd34afaea9 100644 --- a/src/Compatibility/ApiCompat/Microsoft.DotNet.PackageValidation/Validators/PackageValidatorOption.cs +++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.PackageValidation/Validators/PackageValidatorOption.cs @@ -13,7 +13,8 @@ public readonly struct PackageValidatorOption(Package package, bool enableStrictMode = false, bool enqueueApiCompatWorkItems = true, bool executeApiCompatWorkItems = true, - Package? baselinePackage = null) + Package? baselinePackage = null, + string[]? baselinePackageFrameworksToIgnore = null) { /// /// The latest package that should be validated. @@ -39,5 +40,14 @@ public readonly struct PackageValidatorOption(Package package, /// The baseline package to validate the latest package. /// public Package? BaselinePackage { get; } = baselinePackage; + + /// + /// A set of frameworks to ignore from the baseline package. + /// Entries are stored with invariant culture and ignored casing. + /// + public HashSet? BaselinePackageFrameworksToIgnore { get; } = + baselinePackageFrameworksToIgnore is not null ? + new HashSet(baselinePackageFrameworksToIgnore, StringComparer.InvariantCultureIgnoreCase) : + null; } } diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ApiCompat.ValidatePackage.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ApiCompat.ValidatePackage.targets index 69aef6dfa407..c0f67cb5086b 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ApiCompat.ValidatePackage.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ApiCompat.ValidatePackage.targets @@ -55,7 +55,8 @@ Copyright (c) .NET Foundation. All rights reserved. SuppressionOutputFile="$(ApiCompatSuppressionOutputFile)" BaselinePackageTargetPath="$(_packageValidationBaselinePath)" RoslynAssembliesPath="$(RoslynAssembliesPath)" - PackageAssemblyReferences="@(PackageValidationReferencePath)" /> + PackageAssemblyReferences="@(PackageValidationReferencePath)" + BaselinePackageFrameworksToIgnore="@(PackageValidationBaselineFrameworkToIgnore)" /> diff --git a/src/Tests/Microsoft.DotNet.PackageValidation.Tests/Validators/BaselinePackageValidatorTests.cs b/src/Tests/Microsoft.DotNet.PackageValidation.Tests/Validators/BaselinePackageValidatorTests.cs index 63620a22a29f..bd0dd40a11ad 100644 --- a/src/Tests/Microsoft.DotNet.PackageValidation.Tests/Validators/BaselinePackageValidatorTests.cs +++ b/src/Tests/Microsoft.DotNet.PackageValidation.Tests/Validators/BaselinePackageValidatorTests.cs @@ -22,18 +22,13 @@ public class BaselinePackageValidatorTests [Fact] public void TfmDroppedInLatestVersion() { - string[] previousFilePaths = new[] - { - @"ref/netcoreapp3.1/TestPackage.dll", - @"ref/netstandard2.0/TestPackage.dll" - }; - Package baselinePackage = new(string.Empty, "TestPackage", "1.0.0", previousFilePaths, null); - - string[] currentFilePaths = new[] - { - @"ref/netcoreapp3.1/TestPackage.dll" - }; - Package package = new(string.Empty, "TestPackage", "2.0.0", currentFilePaths, null); + Package baselinePackage = new(string.Empty, "TestPackage", "1.0.0", + [ + @"lib/netcoreapp3.1/TestPackage.dll", + @"lib/netstandard2.0/TestPackage.dll" + ]); + Package package = new(string.Empty, "TestPackage", "2.0.0", [ @"lib/netcoreapp3.1/TestPackage.dll" ]); + (SuppressibleTestLog log, BaselinePackageValidator validator) = CreateLoggerAndValidator(); validator.Validate(new PackageValidatorOption(package, @@ -44,5 +39,26 @@ public void TfmDroppedInLatestVersion() Assert.NotEmpty(log.errors); Assert.Contains(DiagnosticIds.TargetFrameworkDropped + " " + string.Format(Resources.MissingTargetFramework, ".NETStandard,Version=v2.0"), log.errors); } + + [Fact] + public void BaselineFrameworksExcluded() + { + Package baselinePackage = new(string.Empty, "TestPackage", "1.0.0", + [ + @"lib/netcoreapp3.1/TestPackage.dll", + @"lib/netstandard2.0/TestPackage.dll" + ]); + Package package = new(string.Empty, "TestPackage", "2.0.0", [ @"lib/netstandard2.0/TestPackage.dll" ]); + + (SuppressibleTestLog log, BaselinePackageValidator validator) = CreateLoggerAndValidator(); + + validator.Validate(new PackageValidatorOption(package, + enableStrictMode: false, + enqueueApiCompatWorkItems: false, + baselinePackage: baselinePackage, + baselinePackageFrameworksToIgnore: [ "netcoreapp3.1" ])); + + Assert.Empty(log.errors); + } } }