@@ -30,6 +30,8 @@ internal sealed partial class NugetPackageRestorer : IDisposable
3030 private readonly DependencyDirectory missingPackageDirectory;
3131 private readonly ILogger logger;
3232 private readonly ICompilationInfoContainer compilationInfoContainer;
33+ private HashSet<string> PrivateRegistryFeeds => dependabotProxy?.RegistryURLs ?? [];
34+ private bool HasPrivateRegistryFeeds => PrivateRegistryFeeds.Any();
3335
3436 public DependencyDirectory PackageDirectory { get; }
3537
@@ -114,34 +116,32 @@ public HashSet<AssemblyLookupLocation> Restore()
114116 logger.LogInfo($"Checking NuGet feed responsiveness: {checkNugetFeedResponsiveness}");
115117 compilationInfoContainer.CompilationInfos.Add(("NuGet feed responsiveness checked", checkNugetFeedResponsiveness ? "1" : "0"));
116118
117- HashSet<string>? explicitFeeds = null;
118- HashSet<string>? allFeeds = null;
119- HashSet<string>? reachableFeeds = [];
119+ HashSet<string> explicitFeeds = [];
120+ string? explicitRestoreSources = null;
120121
121122 try
122123 {
124+ HashSet<string> reachableFeeds = [];
125+ HashSet<string> allFeeds = [];
126+
127+ // Find feeds that are configured in NuGet.config files and divide them into ones that
128+ // are explicitly configured for the project or by a private registry, and "all feeds"
129+ // (including inherited ones) from other locations on the host outside of the working directory.
130+ (explicitFeeds, allFeeds) = GetAllFeeds();
131+
123132 if (checkNugetFeedResponsiveness)
124133 {
125- // Find feeds that are configured in NuGet.config files and divide them into ones that
126- // are explicitly configured for the project, and "all feeds" (including inherited ones)
127- // from other locations on the host outside of the working directory.
128- (explicitFeeds, allFeeds) = GetAllFeeds();
129134 var inheritedFeeds = allFeeds.Except(explicitFeeds).ToHashSet();
130135
131- // If private package registries are configured for C#, then consider those
132- // in addition to the ones that are configured in `nuget.config` files.
133- dependabotProxy?.RegistryURLs.ForEach(url => explicitFeeds.Add(url));
134-
135- var (explicitFeedsReachable, reachableExplicitFeeds) = CheckSpecifiedFeeds(explicitFeeds);
136- reachableFeeds.UnionWith(reachableExplicitFeeds);
137-
138- reachableFeeds.UnionWith(GetReachableNuGetFeeds(inheritedFeeds, isFallback: false));
139-
140136 if (inheritedFeeds.Count > 0)
141137 {
142138 compilationInfoContainer.CompilationInfos.Add(("Inherited NuGet feed count", inheritedFeeds.Count.ToString()));
143139 }
144140
141+ var explicitFeedsReachable = CheckSpecifiedFeeds(explicitFeeds, out var reachableExplicitFeeds);
142+ reachableFeeds.UnionWith(reachableExplicitFeeds);
143+ reachableFeeds.UnionWith(GetReachableNuGetFeeds(inheritedFeeds, isFallback: false));
144+
145145 if (!explicitFeedsReachable)
146146 {
147147 // todo: we could also check the reachability of the inherited nuget feeds, but to use those in the fallback we would need to handle authentication too.
@@ -150,6 +150,16 @@ public HashSet<AssemblyLookupLocation> Restore()
150150 ? []
151151 : [unresponsiveMissingPackageLocation];
152152 }
153+
154+ // If feed responsiveness is being checked, we only want to use the feeds that are reachable (note this set includes private
155+ // registry feeds if they are reachable).
156+ explicitRestoreSources = MakeRestoreSourcesArgument(reachableFeeds);
157+ }
158+ else if (HasPrivateRegistryFeeds)
159+ {
160+ // If private registries are configured they need to be included as sources for the restore, which requires that
161+ // they are provided as source arguments for the restore. The private registries are included in the `allFeeds` set.
162+ explicitRestoreSources = MakeRestoreSourcesArgument(allFeeds);
153163 }
154164
155165 using (var nuget = new NugetExeWrapper(fileProvider, legacyPackageDirectory, logger))
@@ -192,10 +202,9 @@ public HashSet<AssemblyLookupLocation> Restore()
192202 }
193203
194204 // Restore project dependencies with `dotnet restore`.
195- // TODO: We also need to respect reachable feeds for resolution restore.
196- var restoredProjects = RestoreSolutions(out var container);
205+ var restoredProjects = RestoreSolutions(explicitRestoreSources, out var container);
197206 var projects = fileProvider.Projects.Except(restoredProjects);
198- RestoreProjects(projects, reachableFeeds , out var containers);
207+ RestoreProjects(projects, explicitRestoreSources , out var containers);
199208
200209 var dependencies = containers.Flatten(container);
201210
@@ -277,7 +286,7 @@ private List<string> GetReachableFallbackNugetFeeds(HashSet<string>? feedsFromNu
277286 /// Populates dependencies with the relevant dependencies from the assets files generated by the restore.
278287 /// Returns a list of projects that are up to date with respect to restore.
279288 /// </summary>
280- private IEnumerable<string> RestoreSolutions(out DependencyContainer dependencies)
289+ private IEnumerable<string> RestoreSolutions(string? explicitRestoreSources, out DependencyContainer dependencies)
281290 {
282291 var successCount = 0;
283292 var nugetSourceFailures = 0;
@@ -288,7 +297,7 @@ private IEnumerable<string> RestoreSolutions(out DependencyContainer dependencie
288297 var projects = fileProvider.Solutions.SelectMany(solution =>
289298 {
290299 logger.LogInfo($"Restoring solution {solution}...");
291- var res = dotnet.Restore(new(solution, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, TargetWindows: isWindows));
300+ var res = dotnet.Restore(new(solution, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, ExtraArgs: explicitRestoreSources, TargetWindows: isWindows));
292301 if (res.Success)
293302 {
294303 successCount++;
@@ -307,39 +316,28 @@ private IEnumerable<string> RestoreSolutions(out DependencyContainer dependencie
307316 return projects;
308317 }
309318
319+ private string? MakeRestoreSourcesArgument(IEnumerable<string> feeds)
320+ {
321+ // Add package sources. If any are present, they override all sources specified in
322+ // the configuration file(s).
323+ var feedArgs = new StringBuilder();
324+ foreach (var feed in feeds)
325+ {
326+ feedArgs.Append($" -s {feed}");
327+ }
328+
329+ return feedArgs.ToString();
330+ }
331+
310332 /// <summary>
311333 /// Executes `dotnet restore` on all projects in projects.
312334 /// This is done in parallel for performance reasons.
313335 /// Populates dependencies with the relative paths to the assets files generated by the restore.
314336 /// </summary>
315337 /// <param name="projects">A list of paths to project files.</param>
316- private void RestoreProjects(IEnumerable<string> projects, HashSet<string>? configuredSources, out ConcurrentBag<DependencyContainer> dependencies)
317- {
318- // Conservatively, we only set this to a non-null value if a Dependabot proxy is enabled.
319- // This ensures that we continue to get the old behaviour where feeds are taken from
320- // `nuget.config` files instead of the command-line arguments.
321- string? extraArgs = null;
322-
323- if (dependabotProxy is not null)
324- {
325- // If the Dependabot proxy is configured, then our main goal is to make `dotnet` aware
326- // of the private registry feeds. However, since providing them as command-line arguments
327- // to `dotnet` ignores other feeds that may be configured, we also need to add the feeds
328- // we have discovered from analysing `nuget.config` files.
329- var sources = configuredSources ?? new();
330- dependabotProxy.RegistryURLs.ForEach(url => sources.Add(url));
331-
332- // Add package sources. If any are present, they override all sources specified in
333- // the configuration file(s).
334- var feedArgs = new StringBuilder();
335- foreach (string source in sources)
336- {
337- feedArgs.Append($" -s {source}");
338- }
339-
340- extraArgs = feedArgs.ToString();
341- }
342-
338+ /// <param name="explicitRestoreSources">The explicit restore sources argument.</param>
339+ private void RestoreProjects(IEnumerable<string> projects, string? explicitRestoreSources, out ConcurrentBag<DependencyContainer> dependencies)
340+ {
343341 var successCount = 0;
344342 var nugetSourceFailures = 0;
345343 ConcurrentBag<DependencyContainer> collectedDependencies = [];
@@ -354,7 +352,7 @@ private void RestoreProjects(IEnumerable<string> projects, HashSet<string>? conf
354352 foreach (var project in projectGroup)
355353 {
356354 logger.LogInfo($"Restoring project {project}...");
357- var res = dotnet.Restore(new(project, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, extraArgs , TargetWindows: isWindows));
355+ var res = dotnet.Restore(new(project, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, ExtraArgs: explicitRestoreSources , TargetWindows: isWindows));
358356 assets.AddDependenciesRange(res.AssetsFilePaths);
359357 lock (sync)
360358 {
@@ -780,20 +778,20 @@ private HashSet<string> GetExcludedFeeds()
780778 /// <param name="feeds">The set of package feeds to check.</param>
781779 /// <returns>
782780 /// True if all feeds are reachable or false otherwise.
783- /// Also returns the list of reachable feeds.
781+ /// Also returns the list of reachable feeds as an out parameter .
784782 /// </returns>
785- private ( bool, List<string>) CheckSpecifiedFeeds(HashSet<string> feeds)
783+ private bool CheckSpecifiedFeeds(HashSet<string> feeds, out List<string> reachableFeeds )
786784 {
787785 // Exclude any feeds that are configured by the corresponding environment variable.
788786 var excludedFeeds = GetExcludedFeeds();
789787
790788 var feedsToCheck = feeds.Where(feed => !excludedFeeds.Contains(feed)).ToHashSet();
791- var reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false);
789+ reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false);
792790 var allFeedsReachable = reachableFeeds.Count == feedsToCheck.Count;
793791
794792 EmitUnreachableFeedsDiagnostics(allFeedsReachable);
795793
796- return ( allFeedsReachable, reachableFeeds) ;
794+ return allFeedsReachable;
797795 }
798796
799797 /// <summary>
@@ -899,13 +897,23 @@ private IEnumerable<string> GetFeeds(Func<IList<string>> getNugetFeeds)
899897 logger.LogDebug("No NuGet feeds found in nuget.config files.");
900898 }
901899
902- // todo: this could be improved.
903- HashSet<string>? allFeeds = null;
900+ // If private package registries are configured for C#, then consider those
901+ // in addition to the ones that are configured in `nuget.config` files.
902+ if (HasPrivateRegistryFeeds)
903+ {
904+ logger.LogInfo($"Found {PrivateRegistryFeeds.Count} private registry feeds configured for C#: {string.Join(", ", PrivateRegistryFeeds.OrderBy(f => f))}");
905+ explicitFeeds.UnionWith(PrivateRegistryFeeds);
906+ }
907+
908+ HashSet<string> allFeeds = [];
909+
910+ // Add all explicitFeeds to the set of all feeds.
911+ allFeeds.UnionWith(explicitFeeds);
904912
905913 if (nugetConfigs.Count > 0)
906914 {
907915 // We don't have to get the feeds from each of the folders from below, it would be enought to check the folders that recursively contain the others.
908- allFeeds = nugetConfigs
916+ var nugetConfigFeeds = nugetConfigs
909917 .Select(config =>
910918 {
911919 try
@@ -922,18 +930,13 @@ private IEnumerable<string> GetFeeds(Func<IList<string>> getNugetFeeds)
922930 .SelectMany(folder => GetFeeds(() => dotnet.GetNugetFeedsFromFolder(folder!)))
923931 .ToHashSet();
924932
925- // If we have discovered any explicit feeds, then we also expect these to be in the set of all feeds.
926- // Normally, it is a safe assumption to make that `GetNugetFeedsFromFolder` will include the feeds configured
927- // in a NuGet configuration file in the given directory. There is one exception: on a system with case-sensitive
928- // file systems, we may discover a configuration file such as `Nuget.Config` which is not recognised by `dotnet nuget`.
929- // In that case, our call to `GetNugetFeeds` will retrieve the feeds from that file (because it is accepted when
930- // provided explicitly as `--configfile` argument), but the call to `GetNugetFeedsFromFolder` will not.
931- allFeeds.UnionWith(explicitFeeds);
933+ allFeeds.UnionWith(nugetConfigFeeds);
932934 }
933935 else
934936 {
935937 // If we haven't found any `nuget.config` files, then obtain a list of feeds from the root source directory.
936- allFeeds = GetFeeds(() => dotnet.GetNugetFeedsFromFolder(fileProvider.SourceDir.FullName)).ToHashSet();
938+ var nugetFeedsFromRoot = GetFeeds(() => dotnet.GetNugetFeedsFromFolder(fileProvider.SourceDir.FullName));
939+ allFeeds.UnionWith(nugetFeedsFromRoot);
937940 }
938941
939942 logger.LogInfo($"Found {allFeeds.Count} NuGet feeds (with inherited ones) in nuget.config files: {string.Join(", ", allFeeds.OrderBy(f => f))}");
0 commit comments