NuGet: Add FindRootDirectory experiment to resolve root entry points#15021
Merged
Conversation
98ad7dc to
13d8522
Compare
Contributor
There was a problem hiding this comment.
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
EntryPointFinderutility that scans the repo for entry points, builds a child→parent map (usingSolutionFile.Parse,SolutionSerializers.SlnXml, and MSBuildProject.FromFilefor.projglob/property/explicit-ref expansion), and walks projects upward to roots. RunWorkerinvokesResolveRootDirectoriesAsyncwhen the newFindRootDirectoryexperiment flag is enabled, replacingJob.Source.Directory/Directories.- Supporting changes:
JobSourceconverted fromclasstorecordto enablewithsyntax, newPathHelper.GetRelativeDirectoryOf, new experiment plumbed throughExperimentsManager, 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}"). SinceILoggerexposes 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; | ||
| } | ||
|
|
sebasgomez238
approved these changes
May 18, 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 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
13d8522 to
89bddb3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When the
nuget-find-root-directoryexperiment 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: