11#pragma warning disable MA0011 // IFormatProvider is missing
22#pragma warning disable MA0047 // Declare types in namespaces
33#pragma warning disable MA0048 // File name must match type name
4+ using System . IO . Compression ;
45using System . Reflection ;
56using System . Text . Json ;
67using Meziantou . Polyfill . Generator ;
78using Microsoft . CodeAnalysis . CSharp ;
89using Microsoft . CodeAnalysis ;
910using Meziantou . Framework ;
1011using System . Text . RegularExpressions ;
12+ using NuGet . Common ;
13+ using NuGet . Protocol ;
14+ using NuGet . Protocol . Core . Types ;
15+ using NuGet . Versioning ;
1116
1217// Reference doesn't contains internal types which may be needed
1318// So, use the runtime types to get all available types and methods
107112}
108113
109114// Detect which TFMs support each polyfill, sort by earliest supported version, then reassign indices
110- DetectAndAssignVersions ( polyfills , compilation , GetRootPath ( ) ) ;
115+ await DetectAndAssignVersionsAsync ( polyfills , compilation , GetRootPath ( ) ) ;
111116
112117polyfills = SortPolyfills ( polyfills ) ;
113118
@@ -435,66 +440,37 @@ string ReadResourceAsString(string name)
435440 return sr . ReadToEnd ( ) ;
436441}
437442
438- static void DetectAndAssignVersions ( Polyfill [ ] polyfills , CSharpCompilation currentCompilation , string rootPath )
443+ static async Task DetectAndAssignVersionsAsync ( Polyfill [ ] polyfills , CSharpCompilation currentCompilation , string rootPath )
439444{
440445 var nugetPackagesPath = GetNuGetPackagesPath ( ) ;
441446 Console . WriteLine ( $ "NuGet packages path: { nugetPackagesPath } ") ;
442447
443- // Define TFMs for netstandard and .NET Framework (stable, fixed versions)
444- var tfmDefinitions = new List < ( string Name , string RefPath ) >
445- {
446- ( "netstandard2.0" , Path . Combine ( nugetPackagesPath , "netstandard.library" , "2.0.3" , "build" , "netstandard2.0" , "ref" ) ) ,
447- ( "netstandard2.1" , Path . Combine ( nugetPackagesPath , "netstandard.library.ref" , "2.1.0" , "ref" , "netstandard2.1" ) ) ,
448- ( "net462" , Path . Combine ( nugetPackagesPath , "microsoft.netframework.referenceassemblies.net462" , "1.0.3" , "build" , ".NETFramework" , "v4.6.2" ) ) ,
449- ( "net472" , Path . Combine ( nugetPackagesPath , "microsoft.netframework.referenceassemblies.net472" , "1.0.3" , "build" , ".NETFramework" , "v4.7.2" ) ) ,
450- ( "net48" , Path . Combine ( nugetPackagesPath , "microsoft.netframework.referenceassemblies.net48" , "1.0.3" , "build" , ".NETFramework" , "v4.8" ) ) ,
448+ // Define required reference assembly packages and their TFM mappings
449+ var requiredPackages = new ( string PackageId , string Version , string TfmName , string RelativeRefPath ) [ ]
450+ {
451+ ( "netstandard.library" , "2.0.3" , "netstandard2.0" , Path . Combine ( "build" , "netstandard2.0" , "ref" ) ) ,
452+ ( "netstandard.library.ref" , "2.1.0" , "netstandard2.1" , Path . Combine ( "ref" , "netstandard2.1" ) ) ,
453+ ( "microsoft.netframework.referenceassemblies.net462" , "1.0.3" , "net462" , Path . Combine ( "build" , ".NETFramework" , "v4.6.2" ) ) ,
454+ ( "microsoft.netframework.referenceassemblies.net472" , "1.0.3" , "net472" , Path . Combine ( "build" , ".NETFramework" , "v4.7.2" ) ) ,
455+ ( "microsoft.netframework.referenceassemblies.net48" , "1.0.3" , "net48" , Path . Combine ( "build" , ".NETFramework" , "v4.8" ) ) ,
456+ ( "microsoft.netcore.app.ref" , "6.0.0" , "net6.0" , Path . Combine ( "ref" , "net6.0" ) ) ,
457+ ( "microsoft.netcore.app.ref" , "7.0.0" , "net7.0" , Path . Combine ( "ref" , "net7.0" ) ) ,
458+ ( "microsoft.netcore.app.ref" , "8.0.0" , "net8.0" , Path . Combine ( "ref" , "net8.0" ) ) ,
459+ ( "microsoft.netcore.app.ref" , "9.0.0" , "net9.0" , Path . Combine ( "ref" , "net9.0" ) ) ,
460+ ( "microsoft.netcore.app.ref" , "10.0.0" , "net10.0" , Path . Combine ( "ref" , "net10.0" ) ) ,
451461 } ;
452462
453- // Dynamically discover Microsoft.NETCore.App.Ref versions from NuGet cache
454- var netcoreAppRefPath = Path . Combine ( nugetPackagesPath , "microsoft.netcore.app.ref" ) ;
455- if ( Directory . Exists ( netcoreAppRefPath ) )
456- {
457- var discoveredVersions = new SortedDictionary < int , ( string TfmName , string RefPath ) > ( ) ;
458- foreach ( var versionDir in Directory . GetDirectories ( netcoreAppRefPath ) )
459- {
460- var versionName = Path . GetFileName ( versionDir ) ;
461- var majorPart = versionName . Split ( '.' ) [ 0 ] ;
462- if ( int . TryParse ( majorPart , out var major ) && major >= 6 && ! discoveredVersions . ContainsKey ( major ) )
463- {
464- var tfmName = $ "net{ major } .0";
465- var refPath = Path . Combine ( versionDir , "ref" , tfmName ) ;
466- if ( Directory . Exists ( refPath ) )
467- {
468- discoveredVersions [ major ] = ( tfmName , refPath ) ;
469- }
470- }
471- }
472-
473- foreach ( var ( _, ( tfmName , refPath ) ) in discoveredVersions )
474- {
475- tfmDefinitions . Add ( ( tfmName , refPath ) ) ;
476- }
477- }
478-
479- // Filter to TFMs with existing reference directories
480- var availableTfms = new List < ( string Name , string RefPath ) > ( ) ;
481- foreach ( var ( name , refPath ) in tfmDefinitions )
482- {
483- if ( Directory . Exists ( refPath ) )
484- {
485- availableTfms . Add ( ( name , refPath ) ) ;
486- }
487- else
488- {
489- Console . WriteLine ( $ "Warning: Reference assemblies not found for { name } at { refPath } ") ;
490- }
491- }
463+ // Build TFM definitions from known package list
464+ var tfmDefinitions = requiredPackages . Select ( p => (
465+ Name : p . TfmName ,
466+ RefPath : Path . Combine ( nugetPackagesPath , p . PackageId , p . Version , p . RelativeRefPath )
467+ ) ) . ToList ( ) ;
492468
493469 // Include current runtime if not already covered
494470 var currentVersionName = $ "net{ Environment . Version . Major } .0";
495- var includeCurrentRuntime = ! availableTfms . Any ( t => string . Equals ( t . Name , currentVersionName , StringComparison . Ordinal ) ) ;
471+ var includeCurrentRuntime = ! tfmDefinitions . Any ( t => string . Equals ( t . Name , currentVersionName , StringComparison . Ordinal ) ) ;
496472
497- var allVersionNames = availableTfms . Select ( t => t . Name ) . ToList ( ) ;
473+ var allVersionNames = tfmDefinitions . Select ( t => t . Name ) . ToList ( ) ;
498474 if ( includeCurrentRuntime )
499475 {
500476 allVersionNames . Add ( currentVersionName ) ;
@@ -507,11 +483,28 @@ static void DetectAndAssignVersions(Polyfill[] polyfills, CSharpCompilation curr
507483 versionOrder [ allVersionNames [ i ] ] = i ;
508484 }
509485
510- // Try to use cached version data to skip expensive assembly loading
486+ // Try to use cached version data to skip expensive assembly loading and package downloading
511487 var cacheFilePath = Path . Combine ( rootPath , "Meziantou.Polyfill.Generator" , "polyfill-supported-versions.json" ) ;
512488 if ( ! TryLoadFromCache ( cacheFilePath , polyfills , allVersionNames ) )
513489 {
514- // Cache miss - load assemblies and check each polyfill
490+ // Cache miss - ensure required packages are downloaded
491+ await EnsurePackagesDownloadedAsync ( nugetPackagesPath , requiredPackages ) ;
492+
493+ // Filter to TFMs with existing reference directories
494+ var availableTfms = new List < ( string Name , string RefPath ) > ( ) ;
495+ foreach ( var ( name , refPath ) in tfmDefinitions )
496+ {
497+ if ( Directory . Exists ( refPath ) )
498+ {
499+ availableTfms . Add ( ( name , refPath ) ) ;
500+ }
501+ else
502+ {
503+ Console . WriteLine ( $ "Warning: Reference assemblies not found for { name } at { refPath } ") ;
504+ }
505+ }
506+
507+ // Load assemblies and check each polyfill
515508 var compilations = new List < ( string Name , int SortOrder , CSharpCompilation Compilation ) > ( ) ;
516509 for ( var i = 0 ; i < availableTfms . Count ; i ++ )
517510 {
@@ -653,6 +646,53 @@ static bool TryLoadFromCache(string cacheFilePath, Polyfill[] polyfills, List<st
653646 return false ;
654647 }
655648 }
649+
650+ static async Task EnsurePackagesDownloadedAsync ( string nugetPackagesPath , ( string PackageId , string Version , string TfmName , string RelativeRefPath ) [ ] packages )
651+ {
652+ var packagesToDownload = packages
653+ . Select ( p => ( p . PackageId , p . Version ) )
654+ . Distinct ( )
655+ . Where ( p => ! Directory . Exists ( Path . Combine ( nugetPackagesPath , p . PackageId , p . Version ) ) )
656+ . ToArray ( ) ;
657+
658+ if ( packagesToDownload . Length == 0 )
659+ {
660+ Console . WriteLine ( "All reference assembly packages already in cache" ) ;
661+ return ;
662+ }
663+
664+ Console . WriteLine ( $ "Downloading { packagesToDownload . Length } missing reference assembly package(s)...") ;
665+
666+ using var cacheContext = new SourceCacheContext ( ) ;
667+ var repository = Repository . Factory . GetCoreV3 ( "https://api.nuget.org/v3/index.json" ) ;
668+ var resource = await repository . GetResourceAsync < FindPackageByIdResource > ( ) ;
669+
670+ foreach ( var ( packageId , version ) in packagesToDownload )
671+ {
672+ Console . WriteLine ( $ " Downloading { packageId } @{ version } ...") ;
673+
674+ using var packageStream = new MemoryStream ( ) ;
675+ var success = await resource . CopyNupkgToStreamAsync (
676+ packageId ,
677+ new NuGetVersion ( version ) ,
678+ packageStream ,
679+ cacheContext ,
680+ NullLogger . Instance ,
681+ CancellationToken . None ) ;
682+
683+ if ( ! success )
684+ throw new InvalidOperationException ( $ "Failed to download NuGet package { packageId } @{ version } ") ;
685+
686+ var packageDir = Path . Combine ( nugetPackagesPath , packageId , version ) ;
687+ packageStream . Seek ( 0 , SeekOrigin . Begin ) ;
688+
689+ Directory . CreateDirectory ( packageDir ) ;
690+ using var archive = new ZipArchive ( packageStream , ZipArchiveMode . Read ) ;
691+ archive . ExtractToDirectory ( packageDir ) ;
692+
693+ Console . WriteLine ( $ " Installed { packageId } @{ version } ") ;
694+ }
695+ }
656696}
657697
658698static Polyfill [ ] SortPolyfills ( Polyfill [ ] items )
0 commit comments