|
8 | 8 | using System.Linq; |
9 | 9 | using System.Threading; |
10 | 10 | using System.Threading.Tasks; |
| 11 | +using NuGet.Client; |
| 12 | +using NuGet.ContentModel; |
11 | 13 | using NuGet.Frameworks; |
12 | 14 | using NuGet.Packaging; |
13 | 15 | using NuGet.Packaging.Core; |
| 16 | +using NuGet.RuntimeModel; |
14 | 17 | using NuGet.Services.Entities; |
15 | 18 | using NuGet.Versioning; |
16 | 19 | using NuGetGallery.Auditing; |
17 | 20 | using NuGetGallery.Packaging; |
18 | 21 | using NuGetGallery.Security; |
| 22 | +using PackageType = NuGet.Packaging.Core.PackageType; |
19 | 23 |
|
20 | 24 | namespace NuGetGallery |
21 | 25 | { |
@@ -698,9 +702,107 @@ public virtual Package EnrichPackageFromNuGetPackage( |
698 | 702 | return package; |
699 | 703 | } |
700 | 704 |
|
701 | | - public virtual IEnumerable<NuGetFramework> GetSupportedFrameworks(PackageArchiveReader package) |
| 705 | + public virtual IEnumerable<NuGetFramework> GetSupportedFrameworks(PackageArchiveReader package) => |
| 706 | + GetSupportedFrameworks(package.NuspecReader, package.GetFiles().ToList()); |
| 707 | + |
| 708 | + /// <summary> |
| 709 | + /// This method combines the logic used in restore operations to make a determination about the TFM supported by the package. |
| 710 | + /// We have curated a set of compatibility requirements for our needs in NuGet.org. The client logic can be found here: |
| 711 | + /// https://github.com/NuGet/NuGet.Client/blob/63255047fe7052cc33b763356ff995d9166f719e/src/NuGet.Core/NuGet.Commands/RestoreCommand/CompatibilityChecker.cs#L252-L294 |
| 712 | + /// https://github.com/NuGet/NuGet.Client/blob/63255047fe7052cc33b763356ff995d9166f719e/src/NuGet.Core/NuGet.Commands/RestoreCommand/CompatibilityChecker.cs#L439-L442 |
| 713 | + /// ...and our combination of these elements is below. |
| 714 | + /// The logic is essentially this: |
| 715 | + /// - Determine whether we're looking at a tools package. In this case we will use tools "pattern sets" (collections of file patterns |
| 716 | + /// defined in <see cref="ManagedCodeConventions" />) to assess which frameworks are targeted by the package. |
| 717 | + /// - If this isn't a tools package, we look for build-time, runtime, content and resource file patterns |
| 718 | + /// For added details on the various cases, see unit tests targeting this method. |
| 719 | + /// </summary> |
| 720 | + public virtual IEnumerable<NuGetFramework> GetSupportedFrameworks(NuspecReader nuspecReader, IList<string> packageFiles) |
| 721 | + { |
| 722 | + var supportedTFMs = Enumerable.Empty<NuGetFramework>(); |
| 723 | + if (packageFiles != null && packageFiles.Any() && nuspecReader != null) |
| 724 | + { |
| 725 | + // Setup content items for analysis |
| 726 | + var items = new ContentItemCollection(); |
| 727 | + items.Load(packageFiles); |
| 728 | + var runtimeGraph = new RuntimeGraph(); |
| 729 | + var conventions = new ManagedCodeConventions(runtimeGraph); |
| 730 | + |
| 731 | + // Let's test for tools packages first--they're a special case |
| 732 | + var groups = Enumerable.Empty<ContentItemGroup>(); |
| 733 | + var packageTypes = nuspecReader.GetPackageTypes(); |
| 734 | + if (packageTypes.Count == 1 && (packageTypes[0] == PackageType.DotnetTool || |
| 735 | + packageTypes[0] == PackageType.DotnetCliTool)) |
| 736 | + { |
| 737 | + // Only a package that is a tool package (and nothing else) will be matched against tools pattern set |
| 738 | + groups = items.FindItemGroups(conventions.Patterns.ToolsAssemblies); |
| 739 | + } |
| 740 | + else |
| 741 | + { |
| 742 | + // Gather together a list of pattern sets indicating the kinds of packages we wish to evaluate |
| 743 | + var patterns = new[] |
| 744 | + { |
| 745 | + conventions.Patterns.CompileRefAssemblies, |
| 746 | + conventions.Patterns.CompileLibAssemblies, |
| 747 | + conventions.Patterns.RuntimeAssemblies, |
| 748 | + conventions.Patterns.ContentFiles, |
| 749 | + conventions.Patterns.ResourceAssemblies, |
| 750 | + }; |
| 751 | + |
| 752 | + // Add MSBuild to this list, but we need to ensure we have package assets before they make the cut. |
| 753 | + // A series of files in the right places won't matter if there's no {id}.props|targets. |
| 754 | + var msbuildPatterns = new[] |
| 755 | + { |
| 756 | + conventions.Patterns.MSBuildFiles, |
| 757 | + conventions.Patterns.MSBuildMultiTargetingFiles, |
| 758 | + }; |
| 759 | + |
| 760 | + // We'll create a set of "groups" --these are content items which satisfy file pattern sets |
| 761 | + var standardGroups = patterns |
| 762 | + .SelectMany(p => items.FindItemGroups(p)); |
| 763 | + |
| 764 | + // Filter out MSBuild assets that don't match the package ID and append to groups we already have |
| 765 | + var packageId = nuspecReader.GetId(); |
| 766 | + var msbuildGroups = msbuildPatterns |
| 767 | + .SelectMany(p => items.FindItemGroups(p)) |
| 768 | + .Where(g => HasBuildItemsForPackageId(g.Items, packageId)); |
| 769 | + groups = standardGroups.Concat(msbuildGroups); |
| 770 | + } |
| 771 | + |
| 772 | + // Now that we have a collection of groups which have made it through the pattern set filter, let's transform them into TFMs |
| 773 | + supportedTFMs = groups |
| 774 | + .SelectMany(p => p.Properties) |
| 775 | + .Where(pair => pair.Key == ManagedCodeConventions.PropertyNames.TargetFrameworkMoniker) |
| 776 | + .Select(pair => pair.Value) |
| 777 | + .Cast<NuGetFramework>() |
| 778 | + .Distinct(); |
| 779 | + } |
| 780 | + |
| 781 | + return supportedTFMs; |
| 782 | + } |
| 783 | + |
| 784 | + private static bool HasBuildItemsForPackageId(IEnumerable<ContentItem> items, string packageId) |
702 | 785 | { |
703 | | - return package.GetSupportedFrameworks(); |
| 786 | + foreach (var item in items) |
| 787 | + { |
| 788 | + var fileName = Path.GetFileName(item.Path); |
| 789 | + if (fileName == PackagingCoreConstants.EmptyFolder) |
| 790 | + { |
| 791 | + return true; |
| 792 | + } |
| 793 | + |
| 794 | + if ($"{packageId}.props".Equals(fileName, StringComparison.OrdinalIgnoreCase)) |
| 795 | + { |
| 796 | + return true; |
| 797 | + } |
| 798 | + |
| 799 | + if ($"{packageId}.targets".Equals(fileName, StringComparison.OrdinalIgnoreCase)) |
| 800 | + { |
| 801 | + return true; |
| 802 | + } |
| 803 | + } |
| 804 | + |
| 805 | + return false; |
704 | 806 | } |
705 | 807 |
|
706 | 808 | private static EmbeddedLicenseFileType GetEmbeddedLicenseType(PackageMetadata packageMetadata) |
|
0 commit comments