33using System . IO ;
44using System . Linq ;
55using System . Net . Http ;
6+ using System . Text ;
67using System . Text . RegularExpressions ;
78using System . Threading ;
89using System . Threading . Tasks ;
@@ -14,12 +15,13 @@ public sealed partial class DependencyManager
1415 {
1516 private void RestoreNugetPackages ( List < FileInfo > allNonBinaryFiles , IEnumerable < string > allProjects , IEnumerable < string > allSolutions , HashSet < AssemblyLookupLocation > dllLocations )
1617 {
18+ var checkNugetFeedResponsiveness = EnvironmentVariables . GetBoolean ( EnvironmentVariableNames . CheckNugetFeedResponsiveness ) ;
1719 try
1820 {
19- var checkNugetFeedResponsiveness = EnvironmentVariables . GetBoolean ( EnvironmentVariableNames . CheckNugetFeedResponsiveness ) ;
2021 if ( checkNugetFeedResponsiveness && ! CheckFeeds ( allNonBinaryFiles ) )
2122 {
22- DownloadMissingPackages ( allNonBinaryFiles , dllLocations , withNugetConfig : false ) ;
23+ // 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.
24+ DownloadMissingPackagesFromSpecificFeeds ( allNonBinaryFiles , dllLocations ) ;
2325 return ;
2426 }
2527
@@ -75,7 +77,35 @@ private void RestoreNugetPackages(List<FileInfo> allNonBinaryFiles, IEnumerable<
7577 dllLocations . UnionWith ( paths . Select ( p => new AssemblyLookupLocation ( p ) ) ) ;
7678
7779 LogAllUnusedPackages ( dependencies ) ;
78- DownloadMissingPackages ( allNonBinaryFiles , dllLocations ) ;
80+
81+ if ( checkNugetFeedResponsiveness )
82+ {
83+ DownloadMissingPackagesFromSpecificFeeds ( allNonBinaryFiles , dllLocations ) ;
84+ }
85+ else
86+ {
87+ DownloadMissingPackages ( allNonBinaryFiles , dllLocations ) ;
88+ }
89+ }
90+
91+ internal const string PublicNugetFeed = "https://api.nuget.org/v3/index.json" ;
92+
93+ private List < string > GetReachableFallbackNugetFeeds ( )
94+ {
95+ var fallbackFeeds = EnvironmentVariables . GetURLs ( EnvironmentVariableNames . FallbackNugetFeeds ) . ToHashSet ( ) ;
96+ if ( fallbackFeeds . Count == 0 )
97+ {
98+ fallbackFeeds . Add ( PublicNugetFeed ) ;
99+ }
100+
101+ logger . LogInfo ( "Checking fallback Nuget feed reachability" ) ;
102+ var reachableFallbackFeeds = fallbackFeeds . Where ( feed => IsFeedReachable ( feed ) ) . ToList ( ) ;
103+ if ( reachableFallbackFeeds . Count == 0 )
104+ {
105+ logger . LogWarning ( "No fallback Nuget feeds are reachable. Skipping fallback Nuget package restoration." ) ;
106+ }
107+
108+ return reachableFallbackFeeds ;
79109 }
80110
81111 /// <summary>
@@ -148,7 +178,16 @@ private void RestoreProjects(IEnumerable<string> projects, out IEnumerable<strin
148178 CompilationInfos . Add ( ( "Failed project restore with package source error" , nugetSourceFailures . ToString ( ) ) ) ;
149179 }
150180
151- private void DownloadMissingPackages ( List < FileInfo > allFiles , ISet < AssemblyLookupLocation > dllLocations , bool withNugetConfig = true )
181+ private void DownloadMissingPackagesFromSpecificFeeds ( List < FileInfo > allNonBinaryFiles , HashSet < AssemblyLookupLocation > dllLocations )
182+ {
183+ var reachableFallbackFeeds = GetReachableFallbackNugetFeeds ( ) ;
184+ if ( reachableFallbackFeeds . Count > 0 )
185+ {
186+ DownloadMissingPackages ( allNonBinaryFiles , dllLocations , withNugetConfig : false , fallbackNugetFeeds : reachableFallbackFeeds ) ;
187+ }
188+ }
189+
190+ private void DownloadMissingPackages ( List < FileInfo > allFiles , HashSet < AssemblyLookupLocation > dllLocations , bool withNugetConfig = true , IEnumerable < string > ? fallbackNugetFeeds = null )
152191 {
153192 var alreadyDownloadedPackages = GetRestoredPackageDirectoryNames ( packageDirectory . DirInfo ) ;
154193 var alreadyDownloadedLegacyPackages = GetRestoredLegacyPackageNames ( ) ;
@@ -181,9 +220,10 @@ private void DownloadMissingPackages(List<FileInfo> allFiles, ISet<AssemblyLooku
181220 }
182221
183222 logger . LogInfo ( $ "Found { notYetDownloadedPackages . Count } packages that are not yet restored") ;
223+ using var tempDir = new TemporaryDirectory ( ComputeTempDirectory ( sourceDir . FullName , "nugetconfig" ) ) ;
184224 var nugetConfig = withNugetConfig
185225 ? GetNugetConfig ( allFiles )
186- : null ;
226+ : CreateFallbackNugetConfig ( fallbackNugetFeeds , tempDir . DirInfo . FullName ) ;
187227
188228 CompilationInfos . Add ( ( "Fallback nuget restore" , notYetDownloadedPackages . Count . ToString ( ) ) ) ;
189229
@@ -209,6 +249,33 @@ private void DownloadMissingPackages(List<FileInfo> allFiles, ISet<AssemblyLooku
209249 dllLocations . Add ( missingPackageDirectory . DirInfo . FullName ) ;
210250 }
211251
252+ private string ? CreateFallbackNugetConfig ( IEnumerable < string > ? fallbackNugetFeeds , string folderPath )
253+ {
254+ if ( fallbackNugetFeeds is null )
255+ {
256+ // We're not overriding the inherited Nuget feeds
257+ return null ;
258+ }
259+
260+ var sb = new StringBuilder ( ) ;
261+ fallbackNugetFeeds . ForEach ( ( feed , index ) => sb . AppendLine ( $ "<add key=\" feed{ index } \" value=\" { feed } \" />") ) ;
262+
263+ var nugetConfigPath = Path . Combine ( folderPath , "nuget.config" ) ;
264+ logger . LogInfo ( $ "Creating fallback nuget.config file { nugetConfigPath } .") ;
265+ File . WriteAllText ( nugetConfigPath ,
266+ $ """
267+ <?xml version="1.0" encoding="utf-8"?>
268+ <configuration>
269+ <packageSources>
270+ <clear />
271+ { sb }
272+ </packageSources>
273+ </configuration>
274+ """ ) ;
275+
276+ return nugetConfigPath ;
277+ }
278+
212279 private string [ ] GetAllNugetConfigs ( List < FileInfo > allFiles ) => allFiles . SelectFileNamesByName ( "nuget.config" ) . ToArray ( ) ;
213280
214281 private string ? GetNugetConfig ( List < FileInfo > allFiles )
@@ -429,18 +496,18 @@ private bool IsFeedReachable(string feed)
429496 private bool CheckFeeds ( List < FileInfo > allFiles )
430497 {
431498 logger . LogInfo ( "Checking Nuget feeds..." ) ;
432- var feeds = GetAllFeeds ( allFiles ) ;
499+ var ( explicitFeeds , allFeeds ) = GetAllFeeds ( allFiles ) ;
500+ var inheritedFeeds = allFeeds . Except ( explicitFeeds ) . ToHashSet ( ) ;
433501
434- var excludedFeeds = Environment . GetEnvironmentVariable ( EnvironmentVariableNames . ExcludedNugetFeedsFromResponsivenessCheck )
435- ? . Split ( " " , StringSplitOptions . RemoveEmptyEntries )
502+ var excludedFeeds = EnvironmentVariables . GetURLs ( EnvironmentVariableNames . ExcludedNugetFeedsFromResponsivenessCheck )
436503 . ToHashSet ( ) ?? [ ] ;
437504
438505 if ( excludedFeeds . Count > 0 )
439506 {
440507 logger . LogInfo ( $ "Excluded Nuget feeds from responsiveness check: { string . Join ( ", " , excludedFeeds . OrderBy ( f => f ) ) } ") ;
441508 }
442509
443- var allFeedsReachable = feeds . All ( feed => excludedFeeds . Contains ( feed ) || IsFeedReachable ( feed ) ) ;
510+ var allFeedsReachable = explicitFeeds . All ( feed => excludedFeeds . Contains ( feed ) || IsFeedReachable ( feed ) ) ;
444511 if ( ! allFeedsReachable )
445512 {
446513 logger . LogWarning ( "Found unreachable Nuget feed in C# analysis with build-mode 'none'. This may cause missing dependencies in the analysis." ) ;
@@ -454,13 +521,19 @@ private bool CheckFeeds(List<FileInfo> allFiles)
454521 ) ) ;
455522 }
456523 CompilationInfos . Add ( ( "All Nuget feeds reachable" , allFeedsReachable ? "1" : "0" ) ) ;
524+
525+ if ( inheritedFeeds . Count > 0 )
526+ {
527+ logger . LogInfo ( $ "Inherited Nuget feeds: { string . Join ( ", " , inheritedFeeds . OrderBy ( f => f ) ) } ") ;
528+ CompilationInfos . Add ( ( "Inherited Nuget feed count" , inheritedFeeds . Count . ToString ( ) ) ) ;
529+ }
530+
457531 return allFeedsReachable ;
458532 }
459533
460- private IEnumerable < string > GetFeeds ( string nugetConfig )
534+ private IEnumerable < string > GetFeeds ( Func < IList < string > > getNugetFeeds )
461535 {
462- logger . LogInfo ( $ "Getting Nuget feeds from '{ nugetConfig } '...") ;
463- var results = dotnet . GetNugetFeeds ( nugetConfig ) ;
536+ var results = getNugetFeeds ( ) ;
464537 var regex = EnabledNugetFeed ( ) ;
465538 foreach ( var result in results )
466539 {
@@ -479,27 +552,63 @@ private IEnumerable<string> GetFeeds(string nugetConfig)
479552 continue ;
480553 }
481554
482- yield return url ;
555+ if ( ! string . IsNullOrWhiteSpace ( url ) )
556+ {
557+ yield return url ;
558+ }
483559 }
484560 }
485561
486- private HashSet < string > GetAllFeeds ( List < FileInfo > allFiles )
562+ private ( HashSet < string > , HashSet < string > ) GetAllFeeds ( List < FileInfo > allFiles )
487563 {
564+ IList < string > GetNugetFeeds ( string nugetConfig )
565+ {
566+ logger . LogInfo ( $ "Getting Nuget feeds from '{ nugetConfig } '...") ;
567+ return dotnet . GetNugetFeeds ( nugetConfig ) ;
568+ }
569+
570+ IList < string > GetNugetFeedsFromFolder ( string folderPath )
571+ {
572+ logger . LogInfo ( $ "Getting Nuget feeds in folder '{ folderPath } '...") ;
573+ return dotnet . GetNugetFeedsFromFolder ( folderPath ) ;
574+ }
575+
488576 var nugetConfigs = GetAllNugetConfigs ( allFiles ) ;
489- var feeds = nugetConfigs
490- . SelectMany ( GetFeeds )
491- . Where ( str => ! string . IsNullOrWhiteSpace ( str ) )
577+ var explicitFeeds = nugetConfigs
578+ . SelectMany ( config => GetFeeds ( ( ) => GetNugetFeeds ( config ) ) )
492579 . ToHashSet ( ) ;
493580
494- if ( feeds . Count > 0 )
581+ if ( explicitFeeds . Count > 0 )
495582 {
496- logger . LogInfo ( $ "Found { feeds . Count } Nuget feeds in nuget.config files: { string . Join ( ", " , feeds . OrderBy ( f => f ) ) } ") ;
583+ logger . LogInfo ( $ "Found { explicitFeeds . Count } Nuget feeds in nuget.config files: { string . Join ( ", " , explicitFeeds . OrderBy ( f => f ) ) } ") ;
497584 }
498585 else
499586 {
500587 logger . LogDebug ( "No Nuget feeds found in nuget.config files." ) ;
501588 }
502- return feeds ;
589+
590+ // todo: this could be improved.
591+ // 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.
592+ var allFeeds = nugetConfigs
593+ . Select ( config =>
594+ {
595+ try
596+ {
597+ return new FileInfo ( config ) . Directory ? . FullName ;
598+ }
599+ catch ( Exception exc )
600+ {
601+ logger . LogWarning ( $ "Failed to get directory of '{ config } ': { exc } ") ;
602+ }
603+ return null ;
604+ } )
605+ . Where ( folder => folder != null )
606+ . SelectMany ( folder => GetFeeds ( ( ) => GetNugetFeedsFromFolder ( folder ! ) ) )
607+ . ToHashSet ( ) ;
608+
609+ logger . LogInfo ( $ "Found { allFeeds . Count } Nuget feeds (with inherited ones) in nuget.config files: { string . Join ( ", " , allFeeds . OrderBy ( f => f ) ) } ") ;
610+
611+ return ( explicitFeeds , allFeeds ) ;
503612 }
504613
505614 [ GeneratedRegex ( @"<TargetFramework>.*</TargetFramework>" , RegexOptions . IgnoreCase | RegexOptions . Compiled | RegexOptions . Singleline ) ]
0 commit comments