Skip to content

Commit

Permalink
Add support for PrunePackageReference in the new resolver (#6142)
Browse files Browse the repository at this point in the history
  • Loading branch information
nkolev92 authored Dec 20, 2024
1 parent be6bde1 commit 8f334f0
Show file tree
Hide file tree
Showing 12 changed files with 1,409 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public async Task<ValueTuple<bool, List<RestoreTargetGraph>, RuntimeGraph>> Reso
// This is guaranteed to be computed before any graph with a RID, so we can assume this will return a value.

// PCL Projects with Supports have a runtime graph but no matching framework.
var runtimeGraphPath = projectTargetFramework?.RuntimeIdentifierGraphPath;
var runtimeGraphPath = projectTargetFramework.RuntimeIdentifierGraphPath;

RuntimeGraph? projectProviderRuntimeGraph = default;
if (runtimeGraphPath != null)
Expand Down Expand Up @@ -152,6 +152,8 @@ public async Task<ValueTuple<bool, List<RestoreTargetGraph>, RuntimeGraph>> Reso
}
}

Dictionary<LibraryDependencyIndex, VersionRange>? prunedPackageVersions = GetAndIndexPackagesToPrune(libraryDependencyInterningTable, projectTargetFramework);

DependencyGraphItem rootProjectRefItem = new()
{
LibraryDependency = initialProject,
Expand Down Expand Up @@ -635,11 +637,22 @@ async static (state) =>
suppressions = currentSuppressions;
}

HashSet<int>? prunedPackageIndices = null;
for (int i = 0; i < refItemResult.Item.Data.Dependencies.Count; i++)
{
LibraryDependency dep = refItemResult.Item.Data.Dependencies[i];
bool isPackage = dep.LibraryRange.TypeConstraintAllows(LibraryDependencyTarget.Package);
bool isDirectPackageReferenceFromRootProject = (currentRefRangeIndex == rootProjectRefItem.LibraryRangeIndex) && isPackage;

LibraryDependencyIndex depIndex = refItemResult.GetDependencyIndexForDependency(i);

if (ShouldPrunePackage(prunedPackageVersions, refItemResult, dep, depIndex, isPackage, isDirectPackageReferenceFromRootProject))
{
prunedPackageIndices ??= [];
prunedPackageIndices.Add(i);
continue;
}

// Skip this node if the VersionRange is null or if its not transitively pinned and PrivateAssets=All
if (dep.LibraryRange.VersionRange == null || (!importRefItem.IsCentrallyPinnedTransitivePackage && suppressions!.Contains(depIndex)))
{
Expand All @@ -648,9 +661,6 @@ async static (state) =>

VersionRange? pinnedVersionRange = null;

bool isPackage = dep.LibraryRange.TypeConstraintAllows(LibraryDependencyTarget.Package);
bool isDirectPackageReferenceFromRootProject = (currentRefRangeIndex == rootProjectRefItem.LibraryRangeIndex) && isPackage;

if (!isDirectPackageReferenceFromRootProject && directPackageReferences?.Contains(depIndex) == true)
{
continue;
Expand Down Expand Up @@ -752,10 +762,19 @@ async static (state) =>
{
foreach (var dep in runtimeDependencies)
{
var libraryDependencyIndex = findLibraryCachedAsyncResult.GetDependencyIndexForDependency(runtimeDependencyIndex);
if (ShouldPrunePackage(prunedPackageVersions, refItemResult, dep, libraryDependencyIndex, isPackage: true, isDirectPackageReferenceFromRootProject: false))
{
prunedPackageIndices ??= [];
prunedPackageIndices.Add(runtimeDependencyIndex);
runtimeDependencyIndex++;
continue;
}

DependencyGraphItem runtimeDependencyGraphItem = new()
{
LibraryDependency = dep,
LibraryDependencyIndex = findLibraryCachedAsyncResult.GetDependencyIndexForDependency(runtimeDependencyIndex),
LibraryDependencyIndex = libraryDependencyIndex,
LibraryRangeIndex = findLibraryCachedAsyncResult.GetRangeIndexForDependency(runtimeDependencyIndex),
Path = LibraryRangeInterningTable.CreatePathToRef(pathToCurrentRef, currentRefRangeIndex),
Parent = currentRefRangeIndex,
Expand Down Expand Up @@ -786,6 +805,12 @@ async static (state) =>
}
}
}

// If the latest item was chosen, keep track of the pruned dependency indices.
if (chosenResolvedItems.TryGetValue(currentRefDependencyIndex, out ResolvedDependencyGraphItem? resolvedGraphItem))
{
resolvedGraphItem.PrunedDependencies = prunedPackageIndices;
}
}

//Now that we've completed import, figure out the short real flattened list
Expand Down Expand Up @@ -829,13 +854,10 @@ async static (state) =>
LibraryRangeIndex[] pathToChosenRef = foundItem.Path;
bool directPackageReferenceFromRootProject = foundItem.IsDirectPackageReferenceFromRootProject;
List<HashSet<LibraryDependencyIndex>> chosenSuppressions = foundItem.Suppressions;

if (findLibraryEntryCache.TryGetValue(chosenRefRangeIndex, out Task<FindLibraryEntryResult>? nodeTask))
{
FindLibraryEntryResult node = await nodeTask;

flattenedGraphItems.Add(node.Item);

for (int i = 0; i < node.Item.Data.Dependencies.Count; i++)
{
var dep = node.Item.Data.Dependencies[i];
Expand All @@ -845,6 +867,11 @@ async static (state) =>
continue;
}

if (foundItem.PrunedDependencies?.Contains(i) == true)
{
continue;
}

if (StringComparer.OrdinalIgnoreCase.Equals(dep.Name, node.Item.Key.Name) || StringComparer.OrdinalIgnoreCase.Equals(dep.Name, rootGraphNode.Key.Name))
{
// Cycle
Expand Down Expand Up @@ -1034,6 +1061,31 @@ async static (state) =>
range: newGraphNode.Key.VersionRange,
child: newGraphNode.Item.Key));
}

if (foundItem.PrunedDependencies?.Count > 0)
{
int dependencyCount = node.Item.Data.Dependencies.Count - foundItem.PrunedDependencies.Count;

List<LibraryDependency> dependencies = dependencyCount > 0 ? new(dependencyCount) : [];

for (int i = 0; dependencyCount > 0 && i < node.Item.Data.Dependencies.Count; i++)
{
if (!foundItem.PrunedDependencies.Contains(i))
{
dependencies.Add(node.Item.Data.Dependencies[i]);
}
}

RemoteResolveResult remoteResolveResult = new RemoteResolveResult()
{
Match = node.Item.Data.Match,
Dependencies = dependencies,
};

node.Item.Data = remoteResolveResult;
}

flattenedGraphItems.Add(node.Item);
}
}

Expand Down Expand Up @@ -1204,6 +1256,66 @@ async static (state) =>
return (_success, allGraphs, allRuntimes);
}

private static Dictionary<LibraryDependencyIndex, VersionRange>? GetAndIndexPackagesToPrune(LibraryDependencyInterningTable libraryDependencyInterningTable, TargetFrameworkInformation? projectTargetFramework)
{
Dictionary<LibraryDependencyIndex, VersionRange>? prunedPackageVersions = null;

if (projectTargetFramework?.PackagesToPrune.Count > 0)
{
prunedPackageVersions = new Dictionary<LibraryDependencyIndex, VersionRange>(capacity: projectTargetFramework.PackagesToPrune.Count);

foreach (var item in projectTargetFramework.PackagesToPrune)
{
LibraryDependencyIndex depIndex = libraryDependencyInterningTable.Intern(item.Value);
prunedPackageVersions[depIndex] = item.Value.VersionRange;
}
}

return prunedPackageVersions;
}

private bool ShouldPrunePackage(
IReadOnlyDictionary<LibraryDependencyIndex, VersionRange>? packagesToPrune,
FindLibraryEntryResult refItemResult,
LibraryDependency dep,
LibraryDependencyIndex libraryDependencyIndex,
bool isPackage,
bool isDirectPackageReferenceFromRootProject)
{
if (packagesToPrune?.TryGetValue(libraryDependencyIndex, out VersionRange? prunableVersion) == true)
{
if (dep.LibraryRange!.VersionRange!.Satisfies(prunableVersion!.MaxVersion!))
{
if (!isPackage)
{
if (SdkAnalysisLevelMinimums.IsEnabled(
_request.Project!.RestoreMetadata!.SdkAnalysisLevel,
_request.Project.RestoreMetadata.UsingMicrosoftNETSdk,
SdkAnalysisLevelMinimums.PruningWarnings))
{
_logger.Log(RestoreLogMessage.CreateWarning(NuGetLogCode.NU1511, string.Format(CultureInfo.CurrentCulture, Strings.Error_RestorePruningProjectReference, dep.Name)));
}
}
else if (isDirectPackageReferenceFromRootProject)
{
if (SdkAnalysisLevelMinimums.IsEnabled(
_request.Project!.RestoreMetadata!.SdkAnalysisLevel,
_request.Project.RestoreMetadata.UsingMicrosoftNETSdk,
SdkAnalysisLevelMinimums.PruningWarnings))
{
_logger.Log(RestoreLogMessage.CreateWarning(NuGetLogCode.NU1510, string.Format(CultureInfo.CurrentCulture, Strings.Error_RestorePruningDirectPackageReference, dep.Name)));
}
}
else
{
_logger.LogDebug(string.Format(CultureInfo.CurrentCulture, Strings.RestoreDebugPruningPackageReference, $"{dep.Name} {dep.LibraryRange.VersionRange.OriginalString}", refItemResult.Item.Key, prunableVersion.MaxVersion));
return true;
}
}
}
return false;
}

private static bool EvictOnTypeConstraint(LibraryDependencyTarget current, LibraryDependencyTarget previous)
{
if (current == previous)
Expand Down Expand Up @@ -1295,6 +1407,8 @@ private class ResolvedDependencyGraphItem
public required LibraryRangeIndex[] Path { get; set; }

public required List<HashSet<LibraryDependencyIndex>> Suppressions { get; set; }

public HashSet<int>? PrunedDependencies { get; set; }
}

internal sealed class LibraryDependencyInterningTable
Expand Down Expand Up @@ -1334,6 +1448,21 @@ public LibraryDependencyIndex Intern(CentralPackageVersion centralPackageVersion

return index;
}

public LibraryDependencyIndex Intern(PrunePackageReference prunePackageReference)
{
lock (_lockObject)
{
string key = prunePackageReference.Name;
if (!_table.TryGetValue(key, out LibraryDependencyIndex index))
{
index = (LibraryDependencyIndex)_nextIndex++;
_table.TryAdd(key, index);
}

return index;
}
}
}

internal sealed class LibraryRangeInterningTable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class LockFileBuilderCache
private readonly ConcurrentDictionary<CriteriaKey, List<(List<SelectionCriteria>, bool)>> _criteriaSets =
new();

private readonly ConcurrentDictionary<(CriteriaKey, string path, string aliases, LibraryIncludeFlags), Lazy<(LockFileTargetLibrary, bool)>> _lockFileTargetLibraryCache =
private readonly ConcurrentDictionary<(CriteriaKey, string path, string aliases, LibraryIncludeFlags, int dependencyCount), Lazy<(LockFileTargetLibrary, bool)>> _lockFileTargetLibraryCache =
new();

/// <summary>
Expand Down Expand Up @@ -104,7 +104,7 @@ public ContentItemCollection GetContentItems(LockFileLibrary library, LocalPacka
/// <summary>
/// Try to get a LockFileTargetLibrary from the cache.
/// </summary>
internal (LockFileTargetLibrary, bool) GetLockFileTargetLibrary(RestoreTargetGraph graph, NuGetFramework framework, LocalPackageInfo localPackageInfo, string aliases, LibraryIncludeFlags libraryIncludeFlags, Func<(LockFileTargetLibrary, bool)> valueFactory)
internal (LockFileTargetLibrary, bool) GetLockFileTargetLibrary(RestoreTargetGraph graph, NuGetFramework framework, LocalPackageInfo localPackageInfo, string aliases, LibraryIncludeFlags libraryIncludeFlags, List<LibraryDependency> dependencies, Func<(LockFileTargetLibrary, bool)> valueFactory)
{
// Comparing RuntimeGraph for equality is very expensive,
// so in case of a request where the RuntimeGraph is not empty we avoid using the cache.
Expand All @@ -114,7 +114,7 @@ public ContentItemCollection GetContentItems(LockFileLibrary library, LocalPacka
localPackageInfo = localPackageInfo ?? throw new ArgumentNullException(nameof(localPackageInfo));
var criteriaKey = new CriteriaKey(graph.TargetGraphName, framework);
var packagePath = localPackageInfo.ExpandedPath;
return _lockFileTargetLibraryCache.GetOrAdd((criteriaKey, packagePath, aliases, libraryIncludeFlags),
return _lockFileTargetLibraryCache.GetOrAdd((criteriaKey, packagePath, aliases, libraryIncludeFlags, dependencies.Count),
key => new Lazy<(LockFileTargetLibrary, bool)>(valueFactory)).Value;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ internal static (LockFileTargetLibrary, bool) CreateLockFileTargetLibrary(
var runtimeIdentifier = targetGraph.RuntimeIdentifier;
var framework = targetFrameworkOverride ?? targetGraph.Framework;

return cache.GetLockFileTargetLibrary(targetGraph, framework, package, aliases, dependencyType,
return cache.GetLockFileTargetLibrary(targetGraph, framework, package, aliases, dependencyType, dependencies,
() =>
{
LockFileTargetLibrary lockFileLib = null;
Expand Down
27 changes: 27 additions & 0 deletions src/NuGet.Core/NuGet.Commands/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/NuGet.Core/NuGet.Commands/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1127,4 +1127,16 @@ NuGet requires HTTPS sources. Refer to https://aka.ms/nuget-https-everywhere for
<value>Audit source '{0}' did not provide any vulnerability data.</value>
<comment>{0} is the source name</comment>
</data>
<data name="RestoreDebugPruningPackageReference" xml:space="preserve">
<value>Pruning the package '{0}' as a dependency of '{1}'. The maximum prunable version is '{2}'</value>
<comment>0 - package id and version, 1 - version</comment>
</data>
<data name="Error_RestorePruningDirectPackageReference" xml:space="preserve">
<value>PackageReference {0} will not be pruned. Consider removing this package from your dependencies, as it is likely unnecessary.</value>
<comment>0 - package id and version</comment>
</data>
<data name="Error_RestorePruningProjectReference" xml:space="preserve">
<value>A ProjectReference cannot be pruned, {0}.</value>
<comment>0 - project reference</comment>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ internal static class SdkAnalysisLevelMinimums
/// </summary>
internal static readonly NuGetVersion HttpErrorSdkAnalysisLevelMinimumValue = new("9.0.100");

/// <summary>
/// Minimum SDK Analysis Level required for warning for packages and projects that cannot be pruned.
/// </summary>
internal static readonly NuGetVersion PruningWarnings = new("10.0.100");

/// <summary>
/// Determines whether the feature is enabled based on the SDK analysis level.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions src/NuGet.Core/NuGet.Common/Errors/NuGetLogCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,16 @@ public enum NuGetLogCode
/// </summary>
NU1509 = 1509,

/// <summary>
/// Direct reference to a package that will not be pruned.
/// </summary>
NU1510 = 1510,

/// <summary>
/// Project references cannot be pruned
/// </summary>
NU1511 = 1511,

/// <summary>
/// Dependency bumped up
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions src/NuGet.Core/NuGet.Common/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#nullable enable
NuGet.Common.NuGetLogCode.NU1509 = 1509 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1510 = 1510 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1511 = 1511 -> NuGet.Common.NuGetLogCode
static NuGet.Common.MSBuildStringUtility.GetNuGetLogCodes(string! s) -> System.Collections.Immutable.ImmutableArray<NuGet.Common.NuGetLogCode>
Loading

0 comments on commit 8f334f0

Please sign in to comment.