Skip to content

Commit 4f2e3f3

Browse files
committed
C#: Add private registries to the set of explicit feeds. Always use specific sources for restoring if private registries are used of if nuget feed reachability check is performed.
1 parent 53c2867 commit 4f2e3f3

File tree

1 file changed

+67
-64
lines changed

1 file changed

+67
-64
lines changed

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs

Lines changed: 67 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)