Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffkl committed Jan 16, 2025
1 parent 57c0d92 commit f75e418
Show file tree
Hide file tree
Showing 9 changed files with 1,763 additions and 1,356 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Threading.Tasks;
using NuGet.Common;
using NuGet.DependencyResolver;
using NuGet.LibraryModel;

namespace NuGet.Commands
{
internal sealed partial class DependencyGraphResolver
{
/// <summary>
/// A class representing a declared dependency graph item to consider for the resolved graph.
/// </summary>
[DebuggerDisplay("{LibraryDependency}, DependencyIndex={LibraryDependencyIndex}, RangeIndex={LibraryRangeIndex}")]
public class DependencyGraphItem
{
/// <summary>
/// Gets or sets a <see cref="Task{TResult}" /> that returns a <see cref="GraphItem{TItem}" /> containing a <see cref="RemoteResolveResult" /> that represents the resolved graph item after looking it up in the configured feeds.
/// </summary>
public required Task<GraphItem<RemoteResolveResult>> FindLibraryTask { get; set; }

/// <summary>
/// Gets or sets a value indicating whether or not this dependency graph item is a transitively pinned dependency.
/// </summary>
public bool IsCentrallyPinnedTransitivePackage { get; set; }

/// <summary>
/// Gets or sets a value indicating whether or not this dependency graph item is a direct package reference from the root project.
/// </summary>
public bool IsDirectPackageReferenceFromRootProject { get; set; }

/// <summary>
/// Gets or sets the <see cref="LibraryDependency" /> used to declare this dependency graph item.
/// </summary>
public required LibraryDependency LibraryDependency { get; set; }

/// <summary>
/// Gets or sets the <see cref="LibraryDependencyIndex" /> associated with this dependency graph item.
/// </summary>
public LibraryDependencyIndex LibraryDependencyIndex { get; set; } = LibraryDependencyIndex.Invalid;

/// <summary>
/// Gets or sets the <see cref="LibraryRangeIndex" /> associated with this dependency graph item.
/// </summary>
public LibraryRangeIndex LibraryRangeIndex { get; set; } = LibraryRangeIndex.Invalid;

/// <summary>
/// Gets or sets the <see cref="LibraryRangeIndex" /> of this dependency graph item's parent.
/// </summary>
public LibraryRangeIndex Parent { get; set; }

/// <summary>
/// Gets or sets an array representing all of the parent <see cref="LibraryRangeIndex" /> values of this dependency graph item.
/// </summary>
public LibraryRangeIndex[] Path { get; set; } = Array.Empty<LibraryRangeIndex>();

/// <summary>
/// Gets or sets a <see cref="HashSet{T}" /> containing <see cref="LibraryDependency" /> objects representing any runtime specific dependencies.
/// </summary>
public HashSet<LibraryDependency>? RuntimeDependencies { get; set; }

/// <summary>
/// Gets or sets a <see cref="HashSet{T}" /> containing <see cref="LibraryDependencyIndex" /> values representing any libraries that should be suppressed under this dependency graph item.
/// </summary>
public HashSet<LibraryDependencyIndex>? Suppressions { get; set; }

/// <summary>
/// Gets the <see cref="GraphItem{TItem}" /> with the <see cref="RemoteResolveResult" /> of this dependency graph item from calling the delegate specified in <see cref="FindLibraryTask" />.
/// </summary>
/// <param name="restoreRequest">The <see cref="RestoreRequest" /> of the current restore.</param>
/// <param name="packagesToPrune">An optional <see cref="IReadOnlyDictionary{TKey, TValue}" /> containing any packages to prune from the defined dependencies.</param>
/// <param name="isRootProject">Indicates whether or not the dependency graph item is the root project.</param>
/// <param name="context">The <see cref="RemoteWalkContext" /> of the restore.</param>
/// <param name="logger">An <see cref="ILogger" /> used for logging.</param>
/// <returns>A <see cref="GraphItem{TItem}" /> with the <see cref="RemoteResolveResult" /> of the result of finding the library.</returns>
public async Task<GraphItem<RemoteResolveResult>> GetGraphItemAsync(
RestoreRequest restoreRequest,
IReadOnlyDictionary<string, PrunePackageReference>? packagesToPrune,
bool isRootProject,
RemoteWalkContext context,
ILogger logger)
{
// Call the task to get the library, this may returned a cached result
GraphItem<RemoteResolveResult> item = await FindLibraryTask;

// If there are no runtime dependencies or packages to prune, just return the original result
if ((RuntimeDependencies == null || RuntimeDependencies.Count == 0) && (packagesToPrune == null || packagesToPrune.Count == 0))
{
return item;
}

// Create a new list that is big enough for the existing items plus any runtime dependencies
List<LibraryDependency> dependencies = new(RuntimeDependencies?.Count ?? 0 + item.Data.Dependencies.Count);

// Loop through the defined dependencies, leaving out any pruned packages or runtime packages that replace them
for (int i = 0; i < item.Data.Dependencies.Count; i++)
{
LibraryDependency dependency = item.Data.Dependencies[i];

// Skip any packages that should be pruned or will be replaced with a runtime dependency
if (ShouldPrunePackage(restoreRequest, packagesToPrune, dependency, item.Key, isRootProject, context, logger)
|| RuntimeDependencies?.Contains(dependency) == true)
{
continue;
}

dependencies.Add(dependency);
}

if (RuntimeDependencies?.Count > 0)
{
// Add any runtime dependencies unless they should be pruned
foreach (LibraryDependency runtimeDependency in RuntimeDependencies)
{
if (ShouldPrunePackage(restoreRequest, packagesToPrune, runtimeDependency, item.Key, isRootProject, context, logger) == true)
{
continue;
}

dependencies.Add(runtimeDependency);
}
}

// Return a new graph item containing the mutated list of dependencies
return new GraphItem<RemoteResolveResult>(item.Key)
{
Data = new RemoteResolveResult()
{
Dependencies = dependencies,
Match = item.Data.Match
}
};
}

/// <summary>
/// Determines whether or not a package should be pruned from the list of defined dependencies.
/// </summary>
/// <param name="restoreRequest">The <see cref="RestoreRequest" /> of the current restore.</param>
/// <param name="packagesToPrune">An optional <see cref="IReadOnlyDictionary{TKey, TValue}" /> containing any packages to prune from the defined dependencies.</param>
/// <param name="dependency">The <see cref="LibraryDependency" /> of the defined dependency.</param>
/// <param name="parentLibrary">The <see cref="LibraryIdentity" /> of the parent library that defined this dependency.</param>
/// <param name="isRootProject">Indicates if the parent library is the root project.</param>
/// <param name="context">The <see cref="RemoteWalkContext" /> of the restore.</param>
/// <param name="logger">An <see cref="ILogger" /> used for logging.</param>
/// <returns><see langword="true" /> if the package should be pruned, otherwise <see langword="false" />.</returns>
private static bool ShouldPrunePackage(
RestoreRequest restoreRequest,
IReadOnlyDictionary<string, PrunePackageReference>? packagesToPrune,
LibraryDependency dependency,
LibraryIdentity parentLibrary,
bool isRootProject,
RemoteWalkContext context,
ILogger logger)
{
// Do not prune the package if the prune list is null, it is not in the list, or if the version is not satisfied by the range of versions to of the library to prune
if (packagesToPrune?.TryGetValue(dependency.Name, out PrunePackageReference? packageToPrune) != true
|| !dependency.LibraryRange!.VersionRange!.Satisfies(packageToPrune!.VersionRange!.MaxVersion!))
{
return false;
}

bool isPackage = dependency.LibraryRange?.TypeConstraintAllows(LibraryDependencyTarget.Package) == true;

if (!isPackage)
{
// Do not prune the package if it is a project reference
if (SdkAnalysisLevelMinimums.IsEnabled(
restoreRequest.Project!.RestoreMetadata!.SdkAnalysisLevel,
restoreRequest.Project.RestoreMetadata.UsingMicrosoftNETSdk,
SdkAnalysisLevelMinimums.PruningWarnings))
{
// Log a warning only if configured
logger.Log(RestoreLogMessage.CreateWarning(NuGetLogCode.NU1511, string.Format(CultureInfo.CurrentCulture, Strings.Error_RestorePruningProjectReference, dependency.Name)));
}

return false;
}

if (isRootProject)
{
// Do not prune direct package references
if (SdkAnalysisLevelMinimums.IsEnabled(
restoreRequest.Project!.RestoreMetadata!.SdkAnalysisLevel,
restoreRequest.Project.RestoreMetadata.UsingMicrosoftNETSdk,
SdkAnalysisLevelMinimums.PruningWarnings))
{
// Log a warning only if configured
logger.Log(RestoreLogMessage.CreateWarning(NuGetLogCode.NU1510, string.Format(CultureInfo.CurrentCulture, Strings.Error_RestorePruningDirectPackageReference, dependency.Name)));
}

return false;
}

logger.LogDebug(string.Format(CultureInfo.CurrentCulture, Strings.RestoreDebugPruningPackageReference, $"{dependency.Name} {dependency.LibraryRange!.VersionRange.OriginalString}", parentLibrary, packageToPrune.VersionRange.MaxVersion));

return true;
}
}
}
}
Loading

0 comments on commit f75e418

Please sign in to comment.