diff --git a/Arcade.sln b/Arcade.sln index 01436af144ab..53198c86c3ed 100644 --- a/Arcade.sln +++ b/Arcade.sln @@ -129,6 +129,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Arcade.Common", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Arcade.Test.Common", "src\Common\Microsoft.Arcade.Test.Common\Microsoft.Arcade.Test.Common.csproj", "{6CA09DC9-E654-4906-A977-1279F6EDC109}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.PackageValidation", "src\Microsoft.DotNet.PackageValidation\Microsoft.DotNet.PackageValidation.csproj", "{B691A17B-B577-431C-AF4D-199BBAC8EC97}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.PackageValidation.Tests", "src\Microsoft.DotNet.PackageValidation.Tests\Microsoft.DotNet.PackageValidation.Tests.csproj", "{8BBF14AC-48F0-4282-910E-48E816021660}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -579,18 +583,6 @@ Global {6E19C6B6-4ADF-4DD6-86CC-6C1624BCDB71}.Release|x64.Build.0 = Release|Any CPU {6E19C6B6-4ADF-4DD6-86CC-6C1624BCDB71}.Release|x86.ActiveCfg = Release|Any CPU {6E19C6B6-4ADF-4DD6-86CC-6C1624BCDB71}.Release|x86.Build.0 = Release|Any CPU - {62B929C4-3D15-4D43-AEFC-2D0BD3CFC20D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {62B929C4-3D15-4D43-AEFC-2D0BD3CFC20D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {62B929C4-3D15-4D43-AEFC-2D0BD3CFC20D}.Debug|x64.ActiveCfg = Debug|Any CPU - {62B929C4-3D15-4D43-AEFC-2D0BD3CFC20D}.Debug|x64.Build.0 = Debug|Any CPU - {62B929C4-3D15-4D43-AEFC-2D0BD3CFC20D}.Debug|x86.ActiveCfg = Debug|Any CPU - {62B929C4-3D15-4D43-AEFC-2D0BD3CFC20D}.Debug|x86.Build.0 = Debug|Any CPU - {62B929C4-3D15-4D43-AEFC-2D0BD3CFC20D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {62B929C4-3D15-4D43-AEFC-2D0BD3CFC20D}.Release|Any CPU.Build.0 = Release|Any CPU - {62B929C4-3D15-4D43-AEFC-2D0BD3CFC20D}.Release|x64.ActiveCfg = Release|Any CPU - {62B929C4-3D15-4D43-AEFC-2D0BD3CFC20D}.Release|x64.Build.0 = Release|Any CPU - {62B929C4-3D15-4D43-AEFC-2D0BD3CFC20D}.Release|x86.ActiveCfg = Release|Any CPU - {62B929C4-3D15-4D43-AEFC-2D0BD3CFC20D}.Release|x86.Build.0 = Release|Any CPU {3376C769-211F-4537-A156-5F841FF7840B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3376C769-211F-4537-A156-5F841FF7840B}.Debug|Any CPU.Build.0 = Debug|Any CPU {3376C769-211F-4537-A156-5F841FF7840B}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -819,6 +811,30 @@ Global {6CA09DC9-E654-4906-A977-1279F6EDC109}.Release|x64.Build.0 = Release|Any CPU {6CA09DC9-E654-4906-A977-1279F6EDC109}.Release|x86.ActiveCfg = Release|Any CPU {6CA09DC9-E654-4906-A977-1279F6EDC109}.Release|x86.Build.0 = Release|Any CPU + {B691A17B-B577-431C-AF4D-199BBAC8EC97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B691A17B-B577-431C-AF4D-199BBAC8EC97}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B691A17B-B577-431C-AF4D-199BBAC8EC97}.Debug|x64.ActiveCfg = Debug|Any CPU + {B691A17B-B577-431C-AF4D-199BBAC8EC97}.Debug|x64.Build.0 = Debug|Any CPU + {B691A17B-B577-431C-AF4D-199BBAC8EC97}.Debug|x86.ActiveCfg = Debug|Any CPU + {B691A17B-B577-431C-AF4D-199BBAC8EC97}.Debug|x86.Build.0 = Debug|Any CPU + {B691A17B-B577-431C-AF4D-199BBAC8EC97}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B691A17B-B577-431C-AF4D-199BBAC8EC97}.Release|Any CPU.Build.0 = Release|Any CPU + {B691A17B-B577-431C-AF4D-199BBAC8EC97}.Release|x64.ActiveCfg = Release|Any CPU + {B691A17B-B577-431C-AF4D-199BBAC8EC97}.Release|x64.Build.0 = Release|Any CPU + {B691A17B-B577-431C-AF4D-199BBAC8EC97}.Release|x86.ActiveCfg = Release|Any CPU + {B691A17B-B577-431C-AF4D-199BBAC8EC97}.Release|x86.Build.0 = Release|Any CPU + {8BBF14AC-48F0-4282-910E-48E816021660}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BBF14AC-48F0-4282-910E-48E816021660}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BBF14AC-48F0-4282-910E-48E816021660}.Debug|x64.ActiveCfg = Debug|Any CPU + {8BBF14AC-48F0-4282-910E-48E816021660}.Debug|x64.Build.0 = Debug|Any CPU + {8BBF14AC-48F0-4282-910E-48E816021660}.Debug|x86.ActiveCfg = Debug|Any CPU + {8BBF14AC-48F0-4282-910E-48E816021660}.Debug|x86.Build.0 = Debug|Any CPU + {8BBF14AC-48F0-4282-910E-48E816021660}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BBF14AC-48F0-4282-910E-48E816021660}.Release|Any CPU.Build.0 = Release|Any CPU + {8BBF14AC-48F0-4282-910E-48E816021660}.Release|x64.ActiveCfg = Release|Any CPU + {8BBF14AC-48F0-4282-910E-48E816021660}.Release|x64.Build.0 = Release|Any CPU + {8BBF14AC-48F0-4282-910E-48E816021660}.Release|x86.ActiveCfg = Release|Any CPU + {8BBF14AC-48F0-4282-910E-48E816021660}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Microsoft.DotNet.PackageValidation.Tests/GetCompatibilePackageTargetFrameworksTests.cs b/src/Microsoft.DotNet.PackageValidation.Tests/GetCompatibilePackageTargetFrameworksTests.cs new file mode 100644 index 000000000000..fa37f0ad1584 --- /dev/null +++ b/src/Microsoft.DotNet.PackageValidation.Tests/GetCompatibilePackageTargetFrameworksTests.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using NuGet.Frameworks; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.DotNet.PackageValidation.Tests +{ + public class GetCompatibilePackageTargetFrameworksTests + { + public GetCompatibilePackageTargetFrameworksTests() + { + GetCompatiblePackageTargetFrameworks.Initialize(); + } + + public static IEnumerable PackageTFMData => new List + { + // single target framework in package + new object[] { new List { FrameworkConstants.CommonFrameworks.NetStandard20}, new List { FrameworkConstants.CommonFrameworks.NetStandard20, FrameworkConstants.CommonFrameworks.NetCoreApp20, FrameworkConstants.CommonFrameworks.Net463, FrameworkConstants.CommonFrameworks.Net461, FrameworkConstants.CommonFrameworks.Net462} }, + new object[] { new List { FrameworkConstants.CommonFrameworks.NetCoreApp20}, new List { FrameworkConstants.CommonFrameworks.NetCoreApp20} }, + new object[] { new List { FrameworkConstants.CommonFrameworks.NetCoreApp21}, new List { FrameworkConstants.CommonFrameworks.NetCoreApp21} }, + new object[] { new List { FrameworkConstants.CommonFrameworks.Net461}, new List { FrameworkConstants.CommonFrameworks.Net461} }, + new object[] { new List { FrameworkConstants.CommonFrameworks.Net45}, new List { FrameworkConstants.CommonFrameworks.Net45} }, + new object[] { new List { FrameworkConstants.CommonFrameworks.NetCoreApp30}, new List { FrameworkConstants.CommonFrameworks.NetCoreApp30} }, + new object[] { new List { FrameworkConstants.CommonFrameworks.NetCoreApp31}, new List { FrameworkConstants.CommonFrameworks.NetCoreApp31} }, + new object[] { new List { FrameworkConstants.CommonFrameworks.NetStandard21}, new List { FrameworkConstants.CommonFrameworks.NetStandard21, FrameworkConstants.CommonFrameworks.NetCoreApp30 } }, + new object[] { new List { FrameworkConstants.CommonFrameworks.NetStandard12}, new List { FrameworkConstants.CommonFrameworks.NetStandard12, FrameworkConstants.CommonFrameworks.Net451 } }, + + // two target frameworks in package + new object[] { new List { FrameworkConstants.CommonFrameworks.NetStandard20, FrameworkConstants.CommonFrameworks.Net461}, new List { FrameworkConstants.CommonFrameworks.NetStandard20, FrameworkConstants.CommonFrameworks.NetCoreApp20, FrameworkConstants.CommonFrameworks.Net463, FrameworkConstants.CommonFrameworks.Net461, FrameworkConstants.CommonFrameworks.Net462} }, + new object[] { new List { FrameworkConstants.CommonFrameworks.NetStandard20, FrameworkConstants.CommonFrameworks.NetCoreApp30}, new List { FrameworkConstants.CommonFrameworks.NetStandard20, FrameworkConstants.CommonFrameworks.NetCoreApp30, FrameworkConstants.CommonFrameworks.NetCoreApp20, FrameworkConstants.CommonFrameworks.Net463, FrameworkConstants.CommonFrameworks.Net461, FrameworkConstants.CommonFrameworks.Net462} }, + new object[] { new List { FrameworkConstants.CommonFrameworks.NetCoreApp30, FrameworkConstants.CommonFrameworks.Net461}, new List { FrameworkConstants.CommonFrameworks.NetCoreApp30, FrameworkConstants.CommonFrameworks.Net461} }, + new object[] { new List { FrameworkConstants.CommonFrameworks.NetCoreApp30, FrameworkConstants.CommonFrameworks.Net50}, new List { FrameworkConstants.CommonFrameworks.NetCoreApp30, FrameworkConstants.CommonFrameworks.Net50} }, + }; + + [Theory] + [MemberData(nameof(PackageTFMData))] + public void GetCompatibleFrameworks(List packageFrameworks, List expectedTestFrameworks) + { + List actualTestFrameworks = GetCompatiblePackageTargetFrameworks.GetTestFrameworks(packageFrameworks); + CollectionsEqual(expectedTestFrameworks, actualTestFrameworks); + } + + private static void CollectionsEqual(IEnumerable T1, IEnumerable T2) + { + foreach (var item in T1) + { + Assert.Contains(item, T2); + } + foreach (var item in T2) + { + Assert.Contains(item, T1); + } + } + } +} diff --git a/src/Microsoft.DotNet.PackageValidation.Tests/Microsoft.DotNet.PackageValidation.Tests.csproj b/src/Microsoft.DotNet.PackageValidation.Tests/Microsoft.DotNet.PackageValidation.Tests.csproj new file mode 100644 index 000000000000..874202d00363 --- /dev/null +++ b/src/Microsoft.DotNet.PackageValidation.Tests/Microsoft.DotNet.PackageValidation.Tests.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.1 + false + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.PackageValidation/GetCompatiblePackageTargetFrameworks.cs b/src/Microsoft.DotNet.PackageValidation/GetCompatiblePackageTargetFrameworks.cs new file mode 100644 index 000000000000..fd0da8aee1a1 --- /dev/null +++ b/src/Microsoft.DotNet.PackageValidation/GetCompatiblePackageTargetFrameworks.cs @@ -0,0 +1,150 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.DotNet.Build.Tasks; +using NuGet.Frameworks; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.DotNet.PackageValidation +{ + public class GetCompatiblePackageTargetFrameworks : BuildTask + { + private static List allTargetFrameworks = allTargetFrameworks = new(); + private static Dictionary> packageTfmMapping = new(); + + [Required] + public string[] PackagePaths { get; set; } + + [Output] + public ITaskItem[] TestProjects { get; set; } + + public override bool Execute() + { + bool result = true; + List testProjects = new List(); + + try + { + Initialize(); + foreach (var packagePath in PackagePaths) + { + Package package = NupkgParser.CreatePackageObject(packagePath); + List packageTargetFrameworks = package.PackageAssets.Where(t => t.AssetType != AssetType.RuntimeAsset).Select(t => t.TargetFramework).Distinct().ToList(); + + List frameworksToTest = GetTestFrameworks(packageTargetFrameworks); + testProjects.AddRange(CreateItemFromTestFramework(package.Title, package.Version, frameworksToTest, GetRidsFromPackage(package))); + } + + // Removing empty items. + TestProjects = testProjects.Where(tfm => tfm.ItemSpec != "").ToArray(); + } + catch (Exception e) + { + Log.LogErrorFromException(e, showStackTrace: false); + } + + return result && !Log.HasLoggedErrors; + } + + public static List GetTestFrameworks(List packageTargetFrameworks) + { + List frameworksToTest = new List(); + + // Testing the package installation on all tfms linked with package targetframeworks. + foreach (var item in packageTargetFrameworks) + { + if (packageTfmMapping.ContainsKey(item)) + frameworksToTest.AddRange(packageTfmMapping[item].ToList()); + } + + // Pruning the test matrix by removing the frameworks we dont want to test. + frameworksToTest = frameworksToTest.Where(tfm => allTargetFrameworks.Contains(tfm)).ToList(); + + // Adding the frameworks in the packages to the test matrix; + frameworksToTest.AddRange(packageTargetFrameworks); + frameworksToTest = frameworksToTest.Distinct().ToList(); + return frameworksToTest; + } + + public static void Initialize() + { + // Defining the set of known frameworks that we care to test + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.NetCoreApp20); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.NetCoreApp21); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.NetCoreApp30); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.NetCoreApp31); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.Net50); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.Net45); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.Net451); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.Net452); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.Net46); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.Net461); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.Net462); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.Net463); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.NetStandard10); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.NetStandard11); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.NetStandard12); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.NetStandard13); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.NetStandard14); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.NetStandard15); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.NetStandard16); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.NetStandard17); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.NetStandard20); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.NetStandard21); + allTargetFrameworks.Add(FrameworkConstants.CommonFrameworks.UAP10); + + // creating a map framework in package => frameworks to test based on default compatibilty mapping. + foreach (var item in DefaultFrameworkMappings.Instance.CompatibilityMappings) + { + NuGetFramework forwardTfm = item.SupportedFrameworkRange.Max; + NuGetFramework reverseTfm = item.TargetFrameworkRange.Min; + if (packageTfmMapping.ContainsKey(forwardTfm)) + { + packageTfmMapping[forwardTfm].Add(reverseTfm); + } + else + { + packageTfmMapping.Add(forwardTfm, new HashSet { reverseTfm }); + } + } + } + + public List CreateItemFromTestFramework(string title, string version, List testFrameworks, string rids) + { + List testprojects = new List(); + foreach (var framework in testFrameworks) + { + var supportedPackage = new TaskItem(title); + supportedPackage.SetMetadata("Version", version); + supportedPackage.SetMetadata("TargetFramework", framework.ToString()); + supportedPackage.SetMetadata("TargetFrameworkShort", framework.GetShortFolderName()); + + if (!String.IsNullOrEmpty(rids)) + { + supportedPackage.SetMetadata("RuntimeIdentifiers", rids); + } + testprojects.Add(supportedPackage); + } + + return testprojects; + } + + public string GetRidsFromPackage(Package package) + { + List rids = new List(); + foreach (var item in package.PackageAssets) + { + if (item.AssetType == AssetType.RuntimeAsset) + { + if (!rids.Contains(item.Rid + "-x64")) + rids.Add(item.Rid + "-x64"); + } + } + return string.Join(";", rids); + } + } +} diff --git a/src/Microsoft.DotNet.PackageValidation/Microsoft.DotNet.PackageValidation.csproj b/src/Microsoft.DotNet.PackageValidation/Microsoft.DotNet.PackageValidation.csproj new file mode 100644 index 000000000000..0ebe3ccb5858 --- /dev/null +++ b/src/Microsoft.DotNet.PackageValidation/Microsoft.DotNet.PackageValidation.csproj @@ -0,0 +1,24 @@ + + + + netcoreapp3.1 + false + MSBuildSdk + false + true + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.PackageValidation/NupkgParser.cs b/src/Microsoft.DotNet.PackageValidation/NupkgParser.cs new file mode 100644 index 000000000000..19e2f0049c6e --- /dev/null +++ b/src/Microsoft.DotNet.PackageValidation/NupkgParser.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using NuGet.Frameworks; +using NuGet.Packaging; +using NuGet.Packaging.Core; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.DotNet.PackageValidation +{ + public class NupkgParser + { + public static Package CreatePackageObject(string packagePath) + { + List packageAssets = new List(); + Dictionary> packageDependencies = new Dictionary>(); + + PackageArchiveReader nupkgReader = new PackageArchiveReader(packagePath); + NuspecReader nuspecReader = nupkgReader.NuspecReader; + + string title = nuspecReader.GetTitle(); + string version = nuspecReader.GetVersion().ToString(); + IEnumerable dependencyGroups = nuspecReader.GetDependencyGroups(); + + foreach (var item in dependencyGroups) + { + packageDependencies.Add(item.TargetFramework, item.Packages.ToList()); + } + + var files = nupkgReader.GetFiles().ToList().Where(t => t.EndsWith(".dll")).Where(t => t.Contains(title + ".dll")); + foreach (var file in files) + { + packageAssets.Add(ExtractAssetFromFile(file)); + } + + return new Package(title, version, packageAssets, packageDependencies); + } + + public static PackageAsset ExtractAssetFromFile(string filePath) + { + PackageAsset asset = null; + if (filePath.StartsWith("ref")) + { + var stringParts = filePath.Split(@"/"); + asset = new PackageAsset(NuGetFramework.Parse(stringParts[1]), null, filePath, AssetType.RefAsset); + } + else if (filePath.StartsWith("lib")) + { + var stringParts = filePath.Split(@"/"); + asset = new PackageAsset(NuGetFramework.Parse(stringParts[1]), null, filePath, AssetType.LibAsset); + + } + else if (filePath.StartsWith("runtimes")) + { + var stringParts = filePath.Split(@"/"); + asset = new PackageAsset(NuGetFramework.Parse(stringParts[3]), stringParts[1], filePath, AssetType.RuntimeAsset); + } + + return asset; + } + } +} diff --git a/src/Microsoft.DotNet.PackageValidation/Package.cs b/src/Microsoft.DotNet.PackageValidation/Package.cs new file mode 100644 index 000000000000..c24624de1ddd --- /dev/null +++ b/src/Microsoft.DotNet.PackageValidation/Package.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using NuGet.Frameworks; +using NuGet.Packaging.Core; +using System.Collections.Generic; + +namespace Microsoft.DotNet.PackageValidation +{ + public class Package + { + public List PackageAssets { get; set; } + public string Title { get; set; } + public string Version { get; set; } + public Dictionary> PackageDependencies { get; set; } + public Package(string title, string version, List packageAssets, Dictionary> packageDependencies) + { + Title = title; + Version = version; + PackageAssets = packageAssets; + PackageDependencies = packageDependencies; + } + } +} diff --git a/src/Microsoft.DotNet.PackageValidation/PackageAsset.cs b/src/Microsoft.DotNet.PackageValidation/PackageAsset.cs new file mode 100644 index 000000000000..b5eff60fea37 --- /dev/null +++ b/src/Microsoft.DotNet.PackageValidation/PackageAsset.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using NuGet.Frameworks; + +namespace Microsoft.DotNet.PackageValidation +{ + public class PackageAsset + { + public NuGetFramework TargetFramework { get; set; } + public string Rid { get; set; } + public string PackagePath { get; set; } + public AssetType AssetType { get; set; } + + public PackageAsset(NuGetFramework targetFramework, string runtimeIdentifier, string packagePath, AssetType assetType) + { + TargetFramework = targetFramework; + Rid = runtimeIdentifier; + PackagePath = packagePath; + AssetType = assetType; + } + } + + public enum AssetType + { + RefAsset = 0, + LibAsset = 1, + RuntimeAsset = 2 + } +} diff --git a/src/Microsoft.DotNet.PackageValidation/build/Microsoft.DotNet.PackageValidation.props b/src/Microsoft.DotNet.PackageValidation/build/Microsoft.DotNet.PackageValidation.props new file mode 100644 index 000000000000..2ef430ca6426 --- /dev/null +++ b/src/Microsoft.DotNet.PackageValidation/build/Microsoft.DotNet.PackageValidation.props @@ -0,0 +1,5 @@ + + + $(MSBuildThisFileDirectory)\..\tools\net5.0\Microsoft.DotNet.PackageValidation.dll + +