Skip to content

NuGet: validate candidate compatibility lazily - #16453

Open
brettfo wants to merge 1 commit into
mainfrom
dev/brettfo/nuget-lazy-compatibility
Open

brettfo wants to merge 1 commit into
mainfrom
dev/brettfo/nuget-lazy-compatibility

Conversation

@brettfo

@brettfo brettfo commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

What are you trying to accomplish?

Reduce NuGet analysis work by separating version discovery from package compatibility validation. Analysis now discovers filtered candidate versions without opening every package, then validates package existence and target-framework compatibility lazily in preferred-version order until it finds a usable candidate.

This avoids downloading or opening package archives that cannot be selected and removes the duplicate existence and compatibility archive access previously performed for attempted candidates.

Part of #16372.

Anything you want to highlight for special attention from reviewers?

GetVersionsByNameAsync and the existing GetVersionsAsync overloads remain compatibility-aware because dependency conflict resolution relies on that behavior. Only AnalyzeWorker uses the new raw candidate-discovery path.

Candidate ordering is unchanged:

  • Normal updates try the highest version first.
  • Security updates try the lowest safe version first.
  • Versions excluded by requirements, ignores, vulnerabilities, update types, or cooldown are removed before package compatibility work.

Existence and compatibility are now evaluated through one package read per attempted package identity. Shared version properties still require every associated package ID to exist and be compatible.

How will you know you've accomplished your goal?

Added regression coverage for:

  • Selecting a compatible version after skipping a newer incompatible version.
  • Selecting the lowest compatible safe version for security updates.
  • Preserving existence-only candidate fallback when the installed package fails the conservative compatibility checker.
  • Requiring all packages sharing a version property to be compatible.
  • Preserving compatibility filtering for existing VersionFinder callers.
  • Treating an existing package as acceptable when there are no project frameworks.
  • Performing no package download during raw discovery and exactly one download for the selected candidate.
  • Reusing a package archive already downloaded into the job cache after source discovery confirms the candidate version.
  • Propagating cancellation through compatibility and cooldown metadata I/O.

Validation:

dotnet test nuget\helpers\lib\NuGetUpdater\NuGetUpdater.Core.Test\NuGetUpdater.Core.Test.csproj --no-restore --filter "FullyQualifiedName~NuGetUpdater.Core.Test.Analyze.AnalyzeWorkerTests|FullyQualifiedName~NuGetUpdater.Core.Test.Analyze.CompatibilityCheckerTests|FullyQualifiedName~NuGetUpdater.Core.Test.Analyze.VersionFinderTests" --nologo --verbosity minimal

Passed: 66, Failed: 0, Skipped: 0.

Checklist

  • I have run the complete test suite to ensure all tests and linters pass.
  • I have thoroughly tested my code changes to ensure they work as expected, including adding additional tests for new functionality.
  • I have written clear and descriptive commit messages.
  • I have provided a detailed description of the changes in the pull request, including the problem it addresses, how it fixes the problem, and any relevant details about the implementation.
  • I have ensured that the code is well-documented and easy to understand.

Copilot AI balanced review requested due to automatic review settings October 1, 2026 23:29
@github-actions github-actions Bot added the L: dotnet:nuget NuGet packages via nuget or dotnet label Oct 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Unconditional candidate compatibility checks regress updates when the installed package already fails the conservative framework checker.

Review effort: Balanced
Findings: 1 High severity

Open (1)
What changed in this PR

Optimizes NuGet candidate selection by deferring package downloads and compatibility checks until candidates are evaluated.

Changes:

  • Adds raw candidate discovery.
  • Combines existence and compatibility validation into one package read.
  • Adds regression and download-count coverage.
File Description
Analyze/​VersionFinder.cs Separates discovery from compatibility filtering.
Analyze/​CompatabilityChecker.cs Adds combined existence and compatibility validation.
Analyze/​AnalyzeWorker.cs Lazily validates ordered candidates.
Analyze/​VersionFinderTests.cs Tests compatibility-free discovery.
Analyze/​CompatibilityCheckerTests.cs Tests packages without project frameworks.
Analyze/​AnalyzeWorkerTests.cs Tests ordering, shared properties, and downloads.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI balanced review requested due to automatic review settings October 2, 2026 16:12
@brettfo
brettfo force-pushed the dev/brettfo/nuget-lazy-compatibility branch from 157a14e to f710ad3 Compare October 2, 2026 16:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Cached archives can incorrectly satisfy package-existence validation without availability from configured sources.

Review effort: Balanced
Findings: 1 High severity · 1 Low severity

Open (2)
Resolved since last review (1)

@brettfo
brettfo marked this pull request as ready for review October 2, 2026 16:37
@brettfo
brettfo requested a review from a team as a code owner October 2, 2026 16:37

@jmprieur jmprieur left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM

One optional improvement: I would add a comment or test explaining that Dependabot may use a package already downloaded in the job’s NuGet cache, but only after confirming that the same version is available from an allowed package source. This behavior is intentional, but it may not be obvious to future maintainers (took me a bit of time)

@brettfo
brettfo force-pushed the dev/brettfo/nuget-lazy-compatibility branch from f710ad3 to 099f6a8 Compare October 5, 2026 20:00
Copilot AI balanced review requested due to automatic review settings October 5, 2026 20:00
@brettfo

brettfo commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

@jmprieur Addressed the optional maintainability suggestion in 099f6a8. I added a regression that makes the two-part contract explicit: candidate discovery first confirms version 1.1.0 is advertised by the configured source, then compatibility selection reuses the matching archive already in the job-scoped NUGET_PACKAGES cache without downloading it again. The rebased focused suite passes 65/65 tests.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comment thread nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs Outdated
Comment thread nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs Outdated
Comment thread nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/CompatabilityChecker.cs Outdated
Copilot AI balanced review requested due to automatic review settings October 5, 2026 20:39
@brettfo
brettfo force-pushed the dev/brettfo/nuget-lazy-compatibility branch from 099f6a8 to 093ea63 Compare October 5, 2026 20:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Discover candidate versions without opening every package, then combine existence and framework compatibility checks while evaluating candidates in preference order.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@brettfo
brettfo force-pushed the dev/brettfo/nuget-lazy-compatibility branch from 093ea63 to 8eeeab8 Compare October 7, 2026 17:02

@jmprieur jmprieur left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Findings: 2 (0 critical, 2 high, 0 medium, 0 low).

Authored against head 8eeeab88.

packageIds,
version,
nugetContext,
cancellationToken);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[high] Confirm allowed-source availability for cached sibling packages

Could we retain source-availability checks for every package sharing the version property before accepting a candidate? Discovery confirms the version only for the primary package, while both new validation branches can read a sibling directly from NUGET_PACKAGES.

If A and B share a property, the configured feed serves A 2.0.0 but not B 2.0.0, and a compatible B 2.0.0 archive is cached, this loop accepts 2.0.0. The removed DoVersionsExistAsync rejected it by checking the allowed sources for each identity. Please keep cache reuse for archive inspection, but independently confirm each sibling's source availability.

cancellationToken)
: await DoAllPackagesExistAsync(
packageIds,
version,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[high] Preserve primary-package compatibility in the fallback path

Could the existence-only fallback still check compatibility for the primary package? Previously GetVersionsAsync(projectFrameworks, ...) filtered primary-package candidates before this loop, even when the installed version failed the checker. Raw discovery removes that filter.

For a net8.0 project with an incompatible installed package, a feed containing compatible 2.0.0 and net9.0-only 3.0.0 previously selected 2.0.0; this branch now selects 3.0.0. An incompatible current sibling can also disable filtering for the primary package. Keeping the primary's compatibility check lazy, while relaxing only sibling compatibility in this fallback, would preserve the previous selection behavior.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L: dotnet:nuget NuGet packages via nuget or dotnet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants