Skip to content

NuGet: Add FindRootDirectory experiment to resolve root entry points#15021

Merged
brettfo merged 1 commit into
mainfrom
dev/brettfo/nuget-root-finder
May 20, 2026
Merged

NuGet: Add FindRootDirectory experiment to resolve root entry points#15021
brettfo merged 1 commit into
mainfrom
dev/brettfo/nuget-root-finder

Conversation

@brettfo
Copy link
Copy Markdown
Contributor

@brettfo brettfo commented May 14, 2026

When the nuget-find-root-directory experiment flag is enabled, the RunWorker scans the repo for .sln, .slnx, and .proj files and builds a child-to-parent mapping. It then walks upward from each job directory's project files to find the ultimate root entry points, replacing the job's directory list with the minimal ancestor set.

This enables Dependabot to discover and update all projects reachable from a common root, even when the job is initially scoped to a single subdirectory.

Includes:

  • EntryPointFinder utility with scanning, parent map building, and root walking
  • MSBuild evaluation for .proj files supporting globs, properties like $(MSBuildThisFileDirectory), and explicit references
  • Integration in RunWorker behind the experiment flag
  • 17 unit tests covering explicit refs, globs, MSBuild properties, transitive chains, cycles, and edge cases
  • 1 end-to-end test proving root directory expansion across a .proj chain with two projects in different directories

@github-actions github-actions Bot added the L: dotnet:nuget NuGet packages via nuget or dotnet label May 14, 2026
@brettfo brettfo force-pushed the dev/brettfo/nuget-root-finder branch from 98ad7dc to 13d8522 Compare May 14, 2026 23:03
@brettfo brettfo marked this pull request as ready for review May 15, 2026 14:49
@brettfo brettfo requested a review from a team as a code owner May 15, 2026 14:49
Copilot AI review requested due to automatic review settings May 15, 2026 14:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Introduces an opt-in nuget-find-root-directory experiment for the NuGet ecosystem that walks .sln/.slnx/.proj parent chains across the whole repo and rewrites the job's directory list to the minimal set of root entry-point directories before update handlers run.

Changes:

  • New EntryPointFinder utility that scans the repo for entry points, builds a child→parent map (using SolutionFile.Parse, SolutionSerializers.SlnXml, and MSBuild Project.FromFile for .proj glob/property/explicit-ref expansion), and walks projects upward to roots.
  • RunWorker invokes ResolveRootDirectoriesAsync when the new FindRootDirectory experiment flag is enabled, replacing Job.Source.Directory/Directories.
  • Supporting changes: JobSource converted from class to record to enable with syntax, new PathHelper.GetRelativeDirectoryOf, new experiment plumbed through ExperimentsManager, plus extensive unit + 1 end-to-end test.
Show a summary per file
File Description
nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/EntryPointFinder.cs New utility implementing entry-point scan, parent map, and root walk
nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs Hooks the experiment in and rewrites the job's Source
nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobSource.cs Converted to record to allow non-destructive mutation
nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs Adds FindRootDirectory flag and serialization key
nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs Adds GetRelativeDirectoryOf helper
nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/EntryPointFinderTests.cs Unit tests covering scanning, glob expansion, MSBuild properties, cycles, etc.
nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/EndToEndTests.cs E2E test that the experiment expands a job's directory through a .proj chain

Copilot's findings

Comments suppressed due to low confidence (1)

nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/EntryPointFinder.cs:145

  • Failures while parsing an entry point file are logged via logger.Info($" Warning: failed to parse {entryPointPath}: {ex.Message}"). Since ILogger exposes severity-specific methods elsewhere in this codebase, prefer using a warning/error log level (or include the exception stack trace) rather than embedding the word "Warning:" in an Info message. Otherwise these failures will be hidden in normal log output and difficult to triage.
        catch (Exception ex)
        {
            logger.Info($"  Warning: failed to parse {entryPointPath}: {ex.Message}");
            return [];
        }
  • Files reviewed: 7/7 changed files
  • Comments generated: 6

Comment on lines +69 to +76
if (rootDirectories.SequenceEqual(originalDirectories))
{
return job;
}

_logger.Info($" Updated job directories from [{string.Join(", ", originalDirectories)}] to [{string.Join(", ", rootDirectories)}]");
var updatedSource = job.Source with { Directory = null, Directories = rootDirectories.ToArray() };
return job with { Source = updatedSource };
Comment on lines +94 to +104
internal static ImmutableArray<string> ScanForEntryPointFiles(string repoRootPath)
{
var result = new List<string>();
foreach (var extension in EntryPointExtensions)
{
var files = Directory.EnumerateFiles(repoRootPath, $"*{extension}", SearchOption.AllDirectories);
result.AddRange(files.Select(f => Path.GetFullPath(f)));
}

return [.. result];
}
Comment on lines +128 to +146
internal static async Task<ImmutableArray<string>> GetChildrenOfEntryPointAsync(string entryPointPath, ILogger logger)
{
var extension = Path.GetExtension(entryPointPath).ToLowerInvariant();
try
{
return extension switch
{
".sln" => GetChildrenOfSolution(entryPointPath),
".slnx" => await GetChildrenOfSlnxAsync(entryPointPath),
".proj" => GetChildrenOfProj(entryPointPath),
_ => [],
};
}
catch (Exception ex)
{
logger.Info($" Warning: failed to parse {entryPointPath}: {ex.Message}");
return [];
}
}
var childToParentMap = await BuildChildToParentMapAsync(entryPointFiles, logger);
if (childToParentMap.Count == 0)
{
logger.Info(" No parent relationships found; keeping original directories.");
Comment on lines +88 to +97
/// <paramref name="rootPath"/>, prefixed with "/". When the file is directly inside the
/// root, the result is "/".
/// </summary>
public static string GetRelativeDirectoryOf(string filePath, string rootPath)
{
var directory = Path.GetDirectoryName(filePath)!;
var relative = Path.GetRelativePath(rootPath, directory).NormalizePathToUnix();
return relative == "." ? "/" : "/" + relative;
}

Comment thread nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs
When the `nuget-find-root-directory` experiment flag is enabled, the
RunWorker scans the repo for .sln, .slnx, and .proj files and builds
a child-to-parent mapping. It then walks upward from each job
directory's project files to find the ultimate root entry points,
replacing the job's directory list with the minimal ancestor set.

This enables Dependabot to discover and update all projects reachable
from a common root, even when the job is initially scoped to a single
subdirectory.

Includes:
- EntryPointFinder utility with scanning, parent map building, and
  root walking
- MSBuild evaluation for .proj files supporting globs, properties
  like $(MSBuildThisFileDirectory), and explicit references
- Integration in RunWorker behind the experiment flag
- 17 unit tests covering explicit refs, globs, MSBuild properties,
  transitive chains, cycles, and edge cases
- 1 end-to-end test proving root directory expansion across a .proj
  chain with two projects in different directories

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@brettfo brettfo force-pushed the dev/brettfo/nuget-root-finder branch from 13d8522 to 89bddb3 Compare May 20, 2026 19:38
@brettfo brettfo merged commit c38c1ad into main May 20, 2026
106 of 107 checks passed
@brettfo brettfo deleted the dev/brettfo/nuget-root-finder branch May 20, 2026 20:06
Copy link
Copy Markdown

@albertoblue87-netizen albertoblue87-netizen left a comment

Choose a reason for hiding this comment

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

Done

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.

4 participants