From 2f1cca6f9dad87757aa56a1f5bad65590ecc2550 Mon Sep 17 00:00:00 2001 From: zznty <94796179+zznty@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:38:03 +0700 Subject: [PATCH] kind of dependency resolver right now plugin to plugin dependencies are not supported --- CringeBootstrap/packages.lock.json | 27 ++--- CringeLauncher/CringeLauncher.csproj | 7 +- CringeLauncher/packages.lock.json | 31 ++---- CringePlugins/CringePlugins.csproj | 5 +- CringePlugins/Loader/PluginsLifetime.cs | 33 +++--- CringePlugins/Resolver/BuiltInPackages.cs | 8 +- CringePlugins/Resolver/PackageResolver.cs | 121 ++++++++++++++++++++-- CringePlugins/packages.lock.json | 19 +--- NuGet/Deps/DependenciesManifest.cs | 25 ++--- 9 files changed, 174 insertions(+), 102 deletions(-) diff --git a/CringeBootstrap/packages.lock.json b/CringeBootstrap/packages.lock.json index 4af8f17..873aa45 100644 --- a/CringeBootstrap/packages.lock.json +++ b/CringeBootstrap/packages.lock.json @@ -18,18 +18,10 @@ "NuGet.Versioning": "6.12.1" } }, - "Basic.Reference.Assemblies.Net80": { + "Basic.Reference.Assemblies.Net90": { "type": "Transitive", "resolved": "1.7.9", - "contentHash": "1wbS9ZJLFVrKD2jqv27gekIrpjpLffR9sitLQh5drWoG9KbyR/CgrAhw5I0c8Eq3zFMOToCmrpZi3VpRoInCgg==", - "dependencies": { - "Microsoft.CodeAnalysis.Common": "4.11.0" - } - }, - "Basic.Reference.Assemblies.Net80Windows": { - "type": "Transitive", - "resolved": "1.7.9", - "contentHash": "98GFm8MC+pv37rTHaxBm5KFucqdJj0jK0XRHSGt2sXK9HNqtGImIFCFahxjUzskQjiUkPAzVhTou2OYZOuhhEg==", + "contentHash": "9CUkU6Z17tJL/cQc6FeZL0qfQf4CBaNpSBqFL6CTmrIC8827x7NEPyNq/inZA/r6Y5ziWo2Yd8caMxLPqDUpeA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "4.11.0" } @@ -70,8 +62,8 @@ }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", - "resolved": "4.11.0", - "contentHash": "djf8ujmqYImFgB04UGtcsEhHrzVqzHowS+EEl/Yunc5LdrYrZhGBWUTXoCF0NzYXJxtfuD+UVQarWpvrNc94Qg==", + "resolved": "4.12.0", + "contentHash": "c1kNYihL2gdcuU1dqm8R8YeA4YkB43TpU3pa2r66Uooh6AAhRtENzj9A4Kj0a+H8JDDyuTjNZql9XlVUzV+UjA==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.3.4", "System.Collections.Immutable": "8.0.0", @@ -80,11 +72,11 @@ }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", - "resolved": "4.11.0", - "contentHash": "6XYi2EusI8JT4y2l/F3VVVS+ISoIX9nqHsZRaG6W5aFeJ5BEuBosHfT/ABb73FN0RZ1Z3cj2j7cL28SToJPXOw==", + "resolved": "4.12.0", + "contentHash": "30vVQ1MizeC22iEdEvI2w0eTIYG43/L20yBzuQH01xKzJgHAoWehzI2F8u07o4mXh4DGMOjQF7aEm0zzvsG3Mg==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "Microsoft.CodeAnalysis.Common": "[4.11.0]", + "Microsoft.CodeAnalysis.Common": "[4.12.0]", "System.Collections.Immutable": "8.0.0", "System.Reflection.Metadata": "8.0.0" } @@ -339,11 +331,10 @@ "cringelauncher": { "type": "Project", "dependencies": { - "Basic.Reference.Assemblies.Net80": "[1.7.9, )", - "Basic.Reference.Assemblies.Net80Windows": "[1.7.9, )", + "Basic.Reference.Assemblies.Net90": "[1.7.9, )", "CringeBootstrap.Abstractions": "[1.0.0, )", "CringePlugins": "[1.0.0, )", - "Microsoft.CodeAnalysis.CSharp": "[4.11.0, )", + "Microsoft.CodeAnalysis.CSharp": "[4.12.0, )", "NLog": "[5.3.4, )", "SpaceEngineersDedicated.ReferenceAssemblies": "[1.*, )", "Steamworks.NET": "[20.1.0, )", diff --git a/CringeLauncher/CringeLauncher.csproj b/CringeLauncher/CringeLauncher.csproj index 886fcaf..857630e 100644 --- a/CringeLauncher/CringeLauncher.csproj +++ b/CringeLauncher/CringeLauncher.csproj @@ -22,10 +22,9 @@ - - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -39,7 +38,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/CringeLauncher/packages.lock.json b/CringeLauncher/packages.lock.json index e19b32b..1c2d8a7 100644 --- a/CringeLauncher/packages.lock.json +++ b/CringeLauncher/packages.lock.json @@ -2,20 +2,11 @@ "version": 1, "dependencies": { "net9.0-windows10.0.19041": { - "Basic.Reference.Assemblies.Net80": { + "Basic.Reference.Assemblies.Net90": { "type": "Direct", "requested": "[1.7.9, )", "resolved": "1.7.9", - "contentHash": "1wbS9ZJLFVrKD2jqv27gekIrpjpLffR9sitLQh5drWoG9KbyR/CgrAhw5I0c8Eq3zFMOToCmrpZi3VpRoInCgg==", - "dependencies": { - "Microsoft.CodeAnalysis.Common": "4.11.0" - } - }, - "Basic.Reference.Assemblies.Net80Windows": { - "type": "Direct", - "requested": "[1.7.9, )", - "resolved": "1.7.9", - "contentHash": "98GFm8MC+pv37rTHaxBm5KFucqdJj0jK0XRHSGt2sXK9HNqtGImIFCFahxjUzskQjiUkPAzVhTou2OYZOuhhEg==", + "contentHash": "9CUkU6Z17tJL/cQc6FeZL0qfQf4CBaNpSBqFL6CTmrIC8827x7NEPyNq/inZA/r6Y5ziWo2Yd8caMxLPqDUpeA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "4.11.0" } @@ -28,18 +19,18 @@ }, "Krafs.Publicizer": { "type": "Direct", - "requested": "[2.2.1, )", - "resolved": "2.2.1", - "contentHash": "QGI4nMGQbKsuFUUboixVHu4mv3lHB5RejIa7toIlzTmwLkuCYYEpUBJjmy3OpXYyj5dVSZAXVbr4oeMSloE67Q==" + "requested": "[2.3.0, )", + "resolved": "2.3.0", + "contentHash": "DjktTgctwxUMhMkWKrRECer3LR1lHzanCOlE4mpinAiY8SfWJq4DG/QitP5h1A+WBjyWHzQSOG+204i3VpO1FA==" }, "Microsoft.CodeAnalysis.CSharp": { "type": "Direct", - "requested": "[4.11.0, )", - "resolved": "4.11.0", - "contentHash": "6XYi2EusI8JT4y2l/F3VVVS+ISoIX9nqHsZRaG6W5aFeJ5BEuBosHfT/ABb73FN0RZ1Z3cj2j7cL28SToJPXOw==", + "requested": "[4.12.0, )", + "resolved": "4.12.0", + "contentHash": "30vVQ1MizeC22iEdEvI2w0eTIYG43/L20yBzuQH01xKzJgHAoWehzI2F8u07o4mXh4DGMOjQF7aEm0zzvsG3Mg==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "Microsoft.CodeAnalysis.Common": "[4.11.0]", + "Microsoft.CodeAnalysis.Common": "[4.12.0]", "System.Collections.Immutable": "8.0.0", "System.Reflection.Metadata": "8.0.0" } @@ -160,8 +151,8 @@ }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", - "resolved": "4.11.0", - "contentHash": "djf8ujmqYImFgB04UGtcsEhHrzVqzHowS+EEl/Yunc5LdrYrZhGBWUTXoCF0NzYXJxtfuD+UVQarWpvrNc94Qg==", + "resolved": "4.12.0", + "contentHash": "c1kNYihL2gdcuU1dqm8R8YeA4YkB43TpU3pa2r66Uooh6AAhRtENzj9A4Kj0a+H8JDDyuTjNZql9XlVUzV+UjA==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.3.4", "System.Collections.Immutable": "8.0.0", diff --git a/CringePlugins/CringePlugins.csproj b/CringePlugins/CringePlugins.csproj index 22dd5ba..b880262 100644 --- a/CringePlugins/CringePlugins.csproj +++ b/CringePlugins/CringePlugins.csproj @@ -22,14 +22,13 @@ - - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/CringePlugins/Loader/PluginsLifetime.cs b/CringePlugins/Loader/PluginsLifetime.cs index 75bd9b4..388a099 100644 --- a/CringePlugins/Loader/PluginsLifetime.cs +++ b/CringePlugins/Loader/PluginsLifetime.cs @@ -25,7 +25,7 @@ public class PluginsLifetime(string gameFolder) : ILoadingStage private ImmutableArray _plugins = []; private readonly DirectoryInfo _dir = Directory.CreateDirectory(Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CringeLauncher")); - private readonly NuGetFramework _runtimeFramework = NuGetFramework.ParseFolder("net8.0-windows10.0.19041.0"); + private readonly NuGetFramework _runtimeFramework = NuGetFramework.ParseFolder("net9.0-windows10.0.19041.0"); public async ValueTask Load(ISplashProgress progress) { @@ -89,35 +89,40 @@ public class PluginsLifetime(string gameFolder) : ILoadingStage Contexts = contextBuilder.ToImmutable(); } - private async Task LoadPlugins(IReadOnlySet packages, PackageSourceMapping sourceMapping, + private async Task LoadPlugins(IReadOnlyCollection packages, PackageSourceMapping sourceMapping, PackagesConfig packagesConfig) { var plugins = _plugins.ToBuilder(); - var packageVersions = BuiltInPackages.GetPackages(_runtimeFramework) - .ToImmutableDictionary(b => b.Package.Id, b => b.Package.Version, - StringComparer.OrdinalIgnoreCase); - - packageVersions = packageVersions.AddRange(packages.Select(b => - new KeyValuePair(b.Package.Id, b.Package.Version))); + var builtInPackages = BuiltInPackages.GetPackages(_runtimeFramework) + .ToImmutableDictionary(package => package.Package.Id); + + var resolvedPackages = builtInPackages.ToDictionary(); + foreach (var package in packages) + { + resolvedPackages.TryAdd(package.Package.Id, package); + } var manifestBuilder = new DependencyManifestBuilder(_dir.CreateSubdirectory("cache"), sourceMapping, - dependency => packageVersions.TryGetValue(dependency.Id, out var version) && version.Major != 99 - ? version - : dependency.Range.MinVersion ?? dependency.Range.MaxVersion); + dependency => + { + resolvedPackages.TryGetValue(dependency.Id, out var package); + return package?.Entry; + }); foreach (var package in packages) { + if (builtInPackages.ContainsKey(package.Package.Id)) continue; + var dir = Path.Join(package.Directory.FullName, "lib", package.ResolvedFramework.GetShortFolderName()); var path = Path.Join(dir, $"{package.Package.Id}.deps.json"); if (!File.Exists(path)) { - await using (var stream = File.Create(path)) - await manifestBuilder.WriteDependencyManifestAsync(stream, package.Entry, _runtimeFramework); + await using var stream = File.Create(path); + await manifestBuilder.WriteDependencyManifestAsync(stream, package.Entry, _runtimeFramework); } - var client = await sourceMapping.GetClientAsync(package.Package.Id); var sourceName = packagesConfig.Sources.First(b => b.Url == client.ToString()).Name; LoadComponent(plugins, Path.Join(dir, $"{package.Package.Id}.dll"), diff --git a/CringePlugins/Resolver/BuiltInPackages.cs b/CringePlugins/Resolver/BuiltInPackages.cs index 050605d..b16e498 100644 --- a/CringePlugins/Resolver/BuiltInPackages.cs +++ b/CringePlugins/Resolver/BuiltInPackages.cs @@ -41,14 +41,14 @@ public static class BuiltInPackages var version = attribute is null ? new(99, 0, 0) : NuGetVersion.Parse((string)attribute.ConstructorArguments[0].Value); return new BuiltInSdkPackage( - new(0, Path.GetFileNameWithoutExtension(r.FileName), version, []), runtimeFramework, + new(0, Path.GetFileNameWithoutExtension(r.FileName), version), runtimeFramework, new(Path.GetFileNameWithoutExtension(r.FileName), version, [new(runtimeFramework, [])], null, [])); } return [ - ..Net80.ReferenceInfos.AllValues.Select(MapSdkPackage), - ..Net80Windows.ReferenceInfos.AllValues.Select(MapSdkPackage), + ..Net90.ReferenceInfos.AllValues.Select(MapSdkPackage), + // ..Net80Windows.ReferenceInfos.AllValues.Select(MapSdkPackage), nlog, se, imGui, @@ -72,7 +72,7 @@ public static class BuiltInPackages dependencies ??= []; return new( - new(0, id, version, [..dependencies.Value.Select(b => b.Id)]), + new(0, id, version), runtimeFramework, new(id, version, [ new(runtimeFramework, dependencies.Value) diff --git a/CringePlugins/Resolver/PackageResolver.cs b/CringePlugins/Resolver/PackageResolver.cs index 29cc0bb..7bcfb10 100644 --- a/CringePlugins/Resolver/PackageResolver.cs +++ b/CringePlugins/Resolver/PackageResolver.cs @@ -11,7 +11,7 @@ namespace CringePlugins.Resolver; public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray references, PackageSourceMapping packageSources) { private static readonly ILogger Log = LogManager.GetCurrentClassLogger(); - public async Task> ResolveAsync() + public async Task> ResolveAsync() { var order = 0; var packages = new SortedDictionary(); @@ -40,10 +40,12 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray.Empty.ToBuilder(); + var set = ImmutableSortedSet.Empty.ToBuilder(); foreach (var (package, catalogEntry) in packages) { var client = await packageSources.GetClientAsync(package.Id); @@ -80,6 +82,78 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray b.TargetFramework == package.ResolvedFramework)?.Dependencies ?? + []; + + foreach (var (id, versionRange) in dependencies) + { + var client = await packageSources.GetClientAsync(id); + + RegistrationRoot? registrationRoot; + + try + { + registrationRoot = await client.GetPackageRegistrationRootAsync(id); + } + catch (HttpRequestException ex) + { + throw new Exception($"Failed to resolve dependency {id} for {package.Package}", ex); + } + + var items = registrationRoot.Items.SelectMany(page => page.Items!) + .ToImmutableDictionary(b => b.CatalogEntry.Version); + + var version = items.Values.Select(b => b.CatalogEntry.Version).OrderDescending().FirstOrDefault(b => versionRange.Satisfies(b)); + + if (version is null) + throw new Exception($"Unable to find version for package {id} as dependency of {package.Package}"); + + var catalogEntry = items[version].CatalogEntry; + + var dependencyPackage = new Package(i, id, version); + + if (packages.TryGetValue(dependencyPackage, out var existingPackage)) + { + if (dependencyPackage.Version < existingPackage.Version) + { + // dependency has lower version than already resolved + // need to check if existing fits the version range + // and reorder existing to ensure it's ordered before requesting package + + if (!versionRange.Satisfies(existingPackage.Version)) + throw new Exception( + $"Incompatible package version {dependencyPackage} (required by {package.Package}) from {existingPackage}"); + + if (dependencyPackage.CompareTo(existingPackage) < 0) + { + packages.Remove(dependencyPackage); + packages.Add(dependencyPackage, existingPackage); + } + + continue; + } + + throw new Exception($"Detected package downgrade from {existingPackage} to {dependencyPackage} as dependency of {package.Package}"); + } + + if (!packages.TryAdd(dependencyPackage, catalogEntry)) + throw new Exception($"Duplicate package {dependencyPackage.Id}"); + + var nearestGroup = NuGetFrameworkUtility.GetNearest(catalogEntry.DependencyGroups ?? [], runtimeFramework, + g => g.TargetFramework); + + if (nearestGroup is null) + throw new Exception($"Unable to find compatible dependency group for {dependencyPackage} as dependency of {package.Package}"); + + set.Add(new RemoteDependencyPackage(dependencyPackage, nearestGroup.TargetFramework, client, package, catalogEntry)); + } + } return set.ToImmutable(); } @@ -127,9 +201,40 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray Dependencies) : IComparable, IComparable +public abstract record ResolvedPackage(Package Package, NuGetFramework ResolvedFramework, CatalogEntry Entry) : IComparable, IComparable +{ + public int CompareTo(ResolvedPackage? other) + { + if (ReferenceEquals(this, other)) return 0; + if (other is null) return 1; + return Package.CompareTo(other.Package); + } + + public int CompareTo(object? obj) + { + if (obj is null) return 1; + if (ReferenceEquals(this, obj)) return 0; + return obj is ResolvedPackage other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(ResolvedPackage)}"); + } + + public override int GetHashCode() => Package.GetHashCode(); + + public virtual bool Equals(Package? other) + { + if (other is null) return false; + return Package.Equals(other); + } +} + +public record Package(int Order, string Id, NuGetVersion Version) : IComparable, IComparable { public int CompareTo(Package? other) { diff --git a/CringePlugins/packages.lock.json b/CringePlugins/packages.lock.json index 77c8da7..50adb13 100644 --- a/CringePlugins/packages.lock.json +++ b/CringePlugins/packages.lock.json @@ -2,20 +2,11 @@ "version": 1, "dependencies": { "net9.0-windows7.0": { - "Basic.Reference.Assemblies.Net80": { + "Basic.Reference.Assemblies.Net90": { "type": "Direct", "requested": "[1.7.9, )", "resolved": "1.7.9", - "contentHash": "1wbS9ZJLFVrKD2jqv27gekIrpjpLffR9sitLQh5drWoG9KbyR/CgrAhw5I0c8Eq3zFMOToCmrpZi3VpRoInCgg==", - "dependencies": { - "Microsoft.CodeAnalysis.Common": "4.11.0" - } - }, - "Basic.Reference.Assemblies.Net80Windows": { - "type": "Direct", - "requested": "[1.7.9, )", - "resolved": "1.7.9", - "contentHash": "98GFm8MC+pv37rTHaxBm5KFucqdJj0jK0XRHSGt2sXK9HNqtGImIFCFahxjUzskQjiUkPAzVhTou2OYZOuhhEg==", + "contentHash": "9CUkU6Z17tJL/cQc6FeZL0qfQf4CBaNpSBqFL6CTmrIC8827x7NEPyNq/inZA/r6Y5ziWo2Yd8caMxLPqDUpeA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "4.11.0" } @@ -39,9 +30,9 @@ }, "Krafs.Publicizer": { "type": "Direct", - "requested": "[2.2.1, )", - "resolved": "2.2.1", - "contentHash": "QGI4nMGQbKsuFUUboixVHu4mv3lHB5RejIa7toIlzTmwLkuCYYEpUBJjmy3OpXYyj5dVSZAXVbr4oeMSloE67Q==" + "requested": "[2.3.0, )", + "resolved": "2.3.0", + "contentHash": "DjktTgctwxUMhMkWKrRECer3LR1lHzanCOlE4mpinAiY8SfWJq4DG/QitP5h1A+WBjyWHzQSOG+204i3VpO1FA==" }, "Lib.Harmony.Thin": { "type": "Direct", diff --git a/NuGet/Deps/DependenciesManifest.cs b/NuGet/Deps/DependenciesManifest.cs index e45eb97..ec72de4 100644 --- a/NuGet/Deps/DependenciesManifest.cs +++ b/NuGet/Deps/DependenciesManifest.cs @@ -56,7 +56,7 @@ public record ManifestPackageKey(string Id, NuGetVersion Version) public override string ToString() => $"{Id}/{Version}"; } -public class DependencyManifestBuilder(DirectoryInfo cacheDirectory, PackageSourceMapping packageSources, Func versionResolver) +public class DependencyManifestBuilder(DirectoryInfo cacheDirectory, PackageSourceMapping packageSources, Func catalogEntryResolver) { private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web) { @@ -100,22 +100,11 @@ public class DependencyManifestBuilder(DirectoryInfo cacheDirectory, PackageSour targets.Add(new(catalogEntry.Id, catalogEntry.Version), await MapEntryAsync(catalogEntry, nearest)); - foreach (var dependency in nearest.Dependencies ?? []) + foreach (var entry in (nearest.Dependencies ?? []).Select(catalogEntryResolver)) { - var client = await packageSources.GetClientAsync(dependency.Id); - var registrationRoot = await client.GetPackageRegistrationRootAsync(dependency.Id); - - var version = versionResolver(dependency)!; - var entry = registrationRoot.Items.SelectMany(b => b.Items ?? []).FirstOrDefault(b => b.CatalogEntry.Version == version)?.CatalogEntry; - if (entry is null) - { - var (url, sleetEntry) = await client.GetPackageRegistrationAsync(dependency.Id, versionResolver(dependency)!); - - entry = sleetEntry; - entry ??= await client.GetPackageCatalogEntryAsync(url); - } - + continue; + await MapCatalogEntryAsync(entry, targetFramework, targets); } } @@ -123,8 +112,10 @@ public class DependencyManifestBuilder(DirectoryInfo cacheDirectory, PackageSour private async Task MapEntryAsync(CatalogEntry entry, DependencyGroup group) { var packageEntries = entry.PackageEntries ?? await GetPackageContent(entry); - - return new(group.Dependencies?.ToImmutableDictionary(b => b.Id, versionResolver) ?? ImmutableDictionary.Empty, + + return new( + group.Dependencies?.ToImmutableDictionary(b => b.Id, b => catalogEntryResolver(b)!.Version) ?? + ImmutableDictionary.Empty, packageEntries.Where(b => b.FullName.StartsWith($"lib/{group.TargetFramework.GetShortFolderName()}/")) .ToImmutableDictionary(b => b.FullName, _ => new RuntimeDependency()), packageEntries.Where(b =>