Skip to content

Commit 5d8076c

Browse files
authored
New TFM logic for package ingestion (#8432)
Improve supported TFM determination for packages
1 parent a3e9ede commit 5d8076c

3 files changed

Lines changed: 310 additions & 3 deletions

File tree

src/NuGetGallery.Services/PackageManagement/IPackageService.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.Linq;
67
using System.Threading.Tasks;
8+
using NuGet.Frameworks;
79
using NuGet.Packaging;
810
using NuGet.Services.Entities;
911
using NuGet.Versioning;
@@ -112,6 +114,8 @@ Package FilterLatestPackageBySuffix(IReadOnlyCollection<Package> packages,
112114

113115
Package EnrichPackageFromNuGetPackage(Package package, PackageArchiveReader packageArchive, PackageMetadata packageMetadata, PackageStreamMetadata packageStreamMetadata, User user);
114116

117+
IEnumerable<NuGetFramework> GetSupportedFrameworks(NuspecReader nuspecReader, IList<string> packageFiles);
118+
115119
Task PublishPackageAsync(string id, string version, bool commitChanges = true);
116120
Task PublishPackageAsync(Package package, bool commitChanges = true);
117121

src/NuGetGallery.Services/PackageManagement/PackageService.cs

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@
88
using System.Linq;
99
using System.Threading;
1010
using System.Threading.Tasks;
11+
using NuGet.Client;
12+
using NuGet.ContentModel;
1113
using NuGet.Frameworks;
1214
using NuGet.Packaging;
1315
using NuGet.Packaging.Core;
16+
using NuGet.RuntimeModel;
1417
using NuGet.Services.Entities;
1518
using NuGet.Versioning;
1619
using NuGetGallery.Auditing;
1720
using NuGetGallery.Packaging;
1821
using NuGetGallery.Security;
22+
using PackageType = NuGet.Packaging.Core.PackageType;
1923

2024
namespace NuGetGallery
2125
{
@@ -698,9 +702,107 @@ public virtual Package EnrichPackageFromNuGetPackage(
698702
return package;
699703
}
700704

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)
702785
{
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;
704806
}
705807

706808
private static EmbeddedLicenseFileType GetEmbeddedLicenseType(PackageMetadata packageMetadata)

0 commit comments

Comments
 (0)