Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor new dependency graph resolution algorithm #6231

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from

Conversation

jeffkl
Copy link
Contributor

@jeffkl jeffkl commented Jan 16, 2025

Bug

Related: https://github.com/NuGet/Client.Engineering/issues/3008

Description

This is a major refactoring of the new dependency resolution algorithm to clean up the code and add more comments. Some of the changes include:

  • Abstracting out major parts of the logic into separate methods so that the main ResolveAsync() method is easier to read
  • Simplifying classes by trimming them down and combining them where it made sense
  • Add XML doc comments to all members
  • Make DependencyGraphResolver a partial class and move its child classes to separate files
  • Went line-by-line and renamed any variable so that it aligned with the final implementation

To validate these changes, I've run restores against some key repositories and verified that the performance is the same and soare the outputs. All tests are passing and the only test updates I made were to improve reliability. One of the tests depended on a specific order of packages which is no longer deterministic due to parallelization.

Once this is merged, I will be working on a markdown file explaining the design and implementation of the algorithm for future contributors.

PR Checklist

  • Meaningful title, helpful description and a linked NuGet/Home issue
  • Added tests
  • Link to an issue or pull request to update docs if this PR changes settings, environment variables, new feature, etc.

@jeffkl jeffkl self-assigned this Jan 16, 2025
@jeffkl jeffkl requested a review from a team as a code owner January 16, 2025 19:07
@jeffkl jeffkl force-pushed the dev-jeffkl-dependencygraphresolver-refactor branch from f75e418 to b9d32cd Compare January 16, 2025 23:11
Copy link
Member

@nkolev92 nkolev92 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is such great work.

I don't really have any big feedback, but a bunch of ideas and comments that I might not even have if we weren't refactoring.

{
// Create a new array, copy the existing path into it, and add the new library range index
LibraryRangeIndex[] newPath = new LibraryRangeIndex[existingPath.Length + 1];

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

format: The spacing here is probably unnecessary.

/// <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; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

            public required Task<GraphItem<RemoteResolveResult>> FindLibraryTask { get; init; }

/// <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; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this, all of these need to be init.

Checked locally it compiles if you do that.

/// <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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like you decide to go back to string lookup for pruning?
Was that intentional?

LibraryDependency dependency,
LibraryIdentity parentLibrary,
bool isRootProject,
RemoteWalkContext context,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unused, now that you're using the logger everywhere.

public GraphItem<RemoteResolveResult> Item { get; }
if (chosenDependencyGraphItemSuppressions.Count == 1 && chosenDependencyGraphItemSuppressions[0].Count == 0 && HasCommonAncestor(chosenResolvedItem.Path, currentDependencyGraphItemPath))
{
// Skip this item if it has no suppressions and has a common ancestor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is confusing to me there's a suppression with no hashset of suppressions and one with.

What's the difference between them?

Do you have a test suggestion that can help me make sense of this?


public LibraryRangeIndex RangeIndex { get; }
bool isCentrallyPinnedTransitivePackage = chosenResolvedItem.IsCentrallyPinnedTransitivePackage;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar as others, unnecessary obfuscation.
This might also warrant a comment.
The idea is that if a package was transitive pinned before, i remains the same.

{
return _dependencyIndices[dependencyIndex];
}
chosenResolvedItem = new ResolvedDependencyGraphItem(currentGraphItem, _indexingTable)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I know it's not used later, but it feels like we shouldn't be reassigning the property we spent all the time comparing against in a non-while loop :D

// 3. This child is not a direct package reference and there is already a direct package reference to it
if (childLibraryDependency.LibraryRange.VersionRange == null
|| (!currentDependencyGraphItem.IsCentrallyPinnedTransitivePackage
&& suppressions!.Contains(childLibraryDependencyIndex))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent this one level further. It's the same condition as 1334.

hasInstallBeenCalledAlready = true;
}

TargetFrameworkInformation? projectTargetFramework = _request.Project.GetTargetFramework(pair.Framework);
// Get the corresponding TargetFrameworkInformation from the restore request
TargetFrameworkInformation? projectTargetFramework = _request.Project.GetTargetFramework(frameworkRuntimePair.Framework);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should never be null.
If it is null, the projectFramewokrRuntimePairs generation is wrong.

Alternatively the framework runtime pairs, can include this type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants