Skip to content

Commit 5766608

Browse files
meziantouCopilot
andcommitted
Replace PackageDownload with lazy NuGet client downloads
Remove PackageDownload items from the Generator csproj and use NuGet client libraries (NuGet.Protocol) to download reference assembly packages on demand. Packages are only downloaded when the polyfill-supported-versions.json cache is invalid, saving time on CI when the cache is up to date. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 9bb3959 commit 5766608

2 files changed

Lines changed: 94 additions & 62 deletions

File tree

Meziantou.Polyfill.Generator/Meziantou.Polyfill.Generator.csproj

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<ItemGroup>
99
<PackageReference Include="Meziantou.Framework.FullPath" Version="1.1.17" />
1010
<PackageReference Include="Microsoft.CodeAnalysis" Version="5.3.0" />
11+
<PackageReference Include="NuGet.Protocol" Version="6.13.1" />
1112
</ItemGroup>
1213

1314
<ItemGroup>
@@ -16,13 +17,4 @@
1617
</EmbeddedResource>
1718
</ItemGroup>
1819

19-
<ItemGroup>
20-
<PackageDownload Include="NETStandard.Library" Version="[2.0.3]" />
21-
<PackageDownload Include="NETStandard.Library.Ref" Version="[2.1.0]" />
22-
<PackageDownload Include="Microsoft.NETFramework.ReferenceAssemblies.net462" Version="[1.0.3]" />
23-
<PackageDownload Include="Microsoft.NETFramework.ReferenceAssemblies.net472" Version="[1.0.3]" />
24-
<PackageDownload Include="Microsoft.NETFramework.ReferenceAssemblies.net48" Version="[1.0.3]" />
25-
<PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[6.0.0];[7.0.0];[8.0.0];[9.0.0];[10.0.0]" />
26-
</ItemGroup>
27-
2820
</Project>

Meziantou.Polyfill.Generator/Program.cs

Lines changed: 93 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
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;
45
using System.Reflection;
56
using System.Text.Json;
67
using Meziantou.Polyfill.Generator;
78
using Microsoft.CodeAnalysis.CSharp;
89
using Microsoft.CodeAnalysis;
910
using Meziantou.Framework;
1011
using 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
@@ -107,7 +112,7 @@
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

112117
polyfills = 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

658698
static Polyfill[] SortPolyfills(Polyfill[] items)

0 commit comments

Comments
 (0)