1+ using Microsoft . Extensions . Logging ;
2+ using Microsoft . Extensions . Options ;
13using NuGet . Common ;
24using NuGet . Packaging ;
35using NuGet . Protocol ;
@@ -64,47 +66,63 @@ internal sealed class NuGetDownloaderPlugin(
6466
6567internal sealed class NuGetDownloader : ICompilerDependencyResolver
6668{
67- private readonly SourceRepository repository ;
6869 private readonly SourceCacheContext cacheContext ;
69- private readonly AsyncLazy < FindPackageByIdResource > findPackageById ;
70+ private readonly ImmutableArray < AsyncLazy < FindPackageByIdResource > > findPackageByIds ;
7071
71- public NuGetDownloader ( )
72+ public NuGetDownloader (
73+ IOptions < NuGetDownloaderOptions > options ,
74+ ILogger < NuGetDownloader > logger )
7275 {
76+ Options = options . Value ;
77+ Logger = logger ;
7378 ImmutableArray < Lazy < INuGetResourceProvider > > providers =
7479 [
7580 new ( ( ) => new RegistrationResourceV3Provider ( ) ) ,
7681 new ( ( ) => new DependencyInfoResourceV3Provider ( ) ) ,
77- new ( ( ) => new CustomHttpHandlerResourceV3Provider ( ) ) ,
82+ new ( ( ) => new CustomHttpHandlerResourceV3Provider ( this ) ) ,
7883 new ( ( ) => new HttpSourceResourceProvider ( ) ) ,
7984 new ( ( ) => new ServiceIndexResourceV3Provider ( ) ) ,
8085 new ( ( ) => new RemoteV3FindPackageByIdResourceProvider ( ) ) ,
8186 ] ;
82- repository = Repository . CreateSource (
87+ IEnumerable < string > sourceUrls =
88+ [
89+ "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" ,
90+ "https://api.nuget.org/v3/index.json" ,
91+ ] ;
92+ var repositories = sourceUrls . Select ( url => Repository . CreateSource (
8393 providers ,
84- "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" ) ;
94+ url ) ) ;
8595 cacheContext = new SourceCacheContext ( ) ;
86- findPackageById = new ( ( ) => repository . GetResourceAsync < FindPackageByIdResource > ( ) ) ;
96+ findPackageByIds = repositories . SelectAsArray ( repository =>
97+ new AsyncLazy < FindPackageByIdResource > ( ( ) => repository . GetResourceAsync < FindPackageByIdResource > ( ) ) ) ;
8798 }
8899
100+ public NuGetDownloaderOptions Options { get ; }
101+ public ILogger < NuGetDownloader > Logger { get ; }
102+
89103 public async Task < CompilerDependency ? > TryResolveCompilerAsync (
90104 CompilerInfo info ,
91105 CompilerVersionSpecifier specifier ,
92106 BuildConfiguration configuration )
93107 {
94- NuGetVersion version ;
108+ ( FindPackageByIdResource ? findPackageById , NuGetVersion version ) result ;
95109 if ( specifier is CompilerVersionSpecifier . NuGetLatest )
96110 {
97- var versions = await ( await findPackageById ) . GetAllVersionsAsync (
98- info . PackageId ,
99- cacheContext ,
100- NullLogger . Instance ,
101- CancellationToken . None ) ;
102- version = versions . FirstOrDefault ( ) ??
111+ var versions = findPackageByIds . ToAsyncEnumerable ( )
112+ . SelectAsync ( static async lazy => await lazy )
113+ . SelectManyAsync ( findPackageById =>
114+ findPackageById . GetAllVersionsAsync (
115+ info . PackageId ,
116+ cacheContext ,
117+ NullLogger . Instance ,
118+ CancellationToken . None ) ,
119+ ( findPackageById , version ) => ( findPackageById , version ) ) ;
120+ result = await versions . FirstOrNullAsync ( ) ??
103121 throw new InvalidOperationException ( $ "Package '{ info . PackageId } ' not found.") ;
104122 }
105123 else if ( specifier is CompilerVersionSpecifier . NuGet nuGetSpecifier )
106124 {
107- version = nuGetSpecifier . Version ;
125+ result = ( null , nuGetSpecifier . Version ) ;
108126 }
109127 else
110128 {
@@ -113,14 +131,27 @@ public NuGetDownloader()
113131
114132 var package = new NuGetDownloadablePackage ( specifier , info . PackageFolder , async ( ) =>
115133 {
134+ var ( findPackageById , version ) = result ;
135+
136+ var finders = findPackageById != null
137+ ? AsyncEnumerable . Create ( findPackageById )
138+ : findPackageByIds . ToAsyncEnumerable ( ) . SelectAsync ( async lazy => await lazy ) ;
139+
116140 var stream = new MemoryStream ( ) ;
117- var success = await ( await findPackageById ) . CopyNupkgToStreamAsync (
118- info . PackageId ,
119- version ,
120- stream ,
121- cacheContext ,
122- NullLogger . Instance ,
123- CancellationToken . None ) ;
141+ var success = await finders . AnyAsync ( async ( findPackageById , cancellationToken ) =>
142+ {
143+ try
144+ {
145+ return await findPackageById . CopyNupkgToStreamAsync (
146+ info . PackageId ,
147+ version ,
148+ stream ,
149+ cacheContext ,
150+ NullLogger . Instance ,
151+ cancellationToken ) ;
152+ }
153+ catch ( Newtonsoft . Json . JsonReaderException ) { return false ; }
154+ } ) ;
124155
125156 if ( ! success )
126157 {
@@ -183,21 +214,24 @@ public async Task<ImmutableArray<LoadedAssembly>> GetAssembliesAsync()
183214
184215internal sealed class CustomHttpHandlerResourceV3Provider : ResourceProvider
185216{
186- public CustomHttpHandlerResourceV3Provider ( )
217+ private readonly NuGetDownloader nuGetDownloader ;
218+
219+ public CustomHttpHandlerResourceV3Provider ( NuGetDownloader nuGetDownloader )
187220 : base ( typeof ( HttpHandlerResource ) , nameof ( CustomHttpHandlerResourceV3Provider ) )
188221 {
222+ this . nuGetDownloader = nuGetDownloader ;
189223 }
190224
191225 public override Task < Tuple < bool , INuGetResource ? > > TryCreate ( SourceRepository source , CancellationToken token )
192226 {
193227 return Task . FromResult ( TryCreate ( source ) ) ;
194228 }
195229
196- private static Tuple < bool , INuGetResource ? > TryCreate ( SourceRepository source )
230+ private Tuple < bool , INuGetResource ? > TryCreate ( SourceRepository source )
197231 {
198232 if ( source . PackageSource . IsHttp )
199233 {
200- var clientHandler = new CorsClientHandler ( ) ;
234+ var clientHandler = new CorsClientHandler ( nuGetDownloader ) ;
201235 var messageHandler = new ServerWarningLogHandler ( clientHandler ) ;
202236 return new ( true , new HttpHandlerResourceV3 ( clientHandler , messageHandler ) ) ;
203237 }
@@ -206,15 +240,31 @@ public CustomHttpHandlerResourceV3Provider()
206240 }
207241}
208242
209- internal sealed class CorsClientHandler : HttpClientHandler
243+ internal sealed class CorsClientHandler ( NuGetDownloader nuGetDownloader ) : HttpClientHandler
210244{
211- protected override Task < HttpResponseMessage > SendAsync ( HttpRequestMessage request , CancellationToken cancellationToken )
245+ protected override async Task < HttpResponseMessage > SendAsync ( HttpRequestMessage request , CancellationToken cancellationToken )
212246 {
213247 if ( request . RequestUri ? . AbsolutePath . EndsWith ( ".nupkg" , StringComparison . OrdinalIgnoreCase ) == true )
214248 {
215249 request . RequestUri = request . RequestUri . WithCorsProxy ( ) ;
216250 }
217251
218- return base . SendAsync ( request , cancellationToken ) ;
252+ var response = await base . SendAsync ( request , cancellationToken ) ;
253+
254+ if ( nuGetDownloader . Options . LogRequests )
255+ {
256+ nuGetDownloader . Logger . LogDebug (
257+ "Sent: {Method} {Uri}, Received: {Status}" ,
258+ request . Method ,
259+ request . RequestUri ,
260+ response . StatusCode ) ;
261+ }
262+
263+ return response ;
219264 }
220265}
266+
267+ internal sealed class NuGetDownloaderOptions
268+ {
269+ public bool LogRequests { get ; set ; }
270+ }
0 commit comments