From 45ba5a03a4928d549dceb0472337cdb21eeee801 Mon Sep 17 00:00:00 2001 From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com> Date: Sun, 2 Nov 2025 15:47:54 +1000 Subject: [PATCH] display requested versions of packages --- .../Evaluation/NugetDependencyGraph.cs | 40 +++++++++++++++++++ .../Features/Evaluation/ProjectEvaluation.cs | 28 ++++++++++--- .../Features/Nuget/NugetClientService.cs | 4 +- .../Features/Nuget/PackageEntry.cs | 15 +++++++ 4 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 src/SharpIDE.Application/Features/Evaluation/NugetDependencyGraph.cs diff --git a/src/SharpIDE.Application/Features/Evaluation/NugetDependencyGraph.cs b/src/SharpIDE.Application/Features/Evaluation/NugetDependencyGraph.cs new file mode 100644 index 0000000..9522877 --- /dev/null +++ b/src/SharpIDE.Application/Features/Evaluation/NugetDependencyGraph.cs @@ -0,0 +1,40 @@ +using NuGet.Packaging.Core; +using NuGet.ProjectModel; + +namespace SharpIDE.Application.Features.Evaluation; + +public class Dependent +{ + public required string PackageName { get; set; } + public required PackageDependency PackageDependency { get; set; } +} +public static class NugetDependencyGraph +{ + internal static Dictionary> GetPackageDependencyMap(LockFile assetsFile) + { + var parentMap = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + var target = assetsFile.Targets.SingleOrDefault(); + if (target == null) return parentMap; + + var packageLibraries = target.Libraries.Where(l => l.Type == "package").ToList(); + + foreach (var library in packageLibraries) + { + var dependencies = library.Dependencies; + foreach (var packageDependency in dependencies) + { + var mapEntry = parentMap!.GetValueOrDefault(packageDependency.Id, []); + var dependent = new Dependent + { + PackageName = library.Name!, + PackageDependency = packageDependency + }; + mapEntry.Add(dependent); + parentMap[packageDependency.Id] = mapEntry; + } + } + + return parentMap; + } +} diff --git a/src/SharpIDE.Application/Features/Evaluation/ProjectEvaluation.cs b/src/SharpIDE.Application/Features/Evaluation/ProjectEvaluation.cs index 494291b..3c8fce9 100644 --- a/src/SharpIDE.Application/Features/Evaluation/ProjectEvaluation.cs +++ b/src/SharpIDE.Application/Features/Evaluation/ProjectEvaluation.cs @@ -1,6 +1,7 @@ using Ardalis.GuardClauses; using Microsoft.Build.Evaluation; using NuGet.ProjectModel; +using NuGet.Versioning; using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; namespace SharpIDE.Application.Features.Evaluation; @@ -64,6 +65,12 @@ public static class ProjectEvaluation public required string TargetFramework { get; set; } public required bool IsTopLevel { get; set; } public required bool IsAutoReferenced { get; set; } + public List? DependentPackages { get; set; } + } + public class DependentPackage + { + public required string PackageName { get; set; } + public required VersionRange RequestedVersion { get; set; } } public static async Task> GetPackageReferencesForProject(SharpIdeProjectModel projectModel, bool includeTransitive = true) { @@ -88,6 +95,7 @@ public static class ProjectEvaluation private static List GetPackagesFromAssetsFile(LockFile assetsFile, bool includeTransitive) { var packages = new List(); + var dependencyMap = NugetDependencyGraph.GetPackageDependencyMap(assetsFile); foreach (var target in assetsFile.Targets.Where(t => t.RuntimeIdentifier == null)) { @@ -102,22 +110,30 @@ public static class ProjectEvaluation .Select(s => s.Name) .ToHashSet(); - foreach (var library in target.Libraries.Where(l => l.Type == "package")) + foreach (var lockFileTargetLibrary in target.Libraries.Where(l => l.Type == "package")) { - var isTopLevel = topLevelDependencies.Contains(library.Name); + var isTopLevel = topLevelDependencies.Contains(lockFileTargetLibrary.Name); if (!includeTransitive && !isTopLevel) continue; var dependency = tfmInfo.Dependencies - .FirstOrDefault(d => d.Name.Equals(library.Name, StringComparison.OrdinalIgnoreCase)); + .FirstOrDefault(d => d.Name.Equals(lockFileTargetLibrary.Name, StringComparison.OrdinalIgnoreCase)); + + var dependents = dependencyMap.GetValueOrDefault(lockFileTargetLibrary.Name, []); + var mappedDependents = dependents.Select(d => new DependentPackage + { + PackageName = d.PackageName, + RequestedVersion = d.PackageDependency.VersionRange + }).ToList(); packages.Add(new InstalledPackage { - Name = library.Name, + Name = lockFileTargetLibrary.Name, RequestedVersion = dependency?.LibraryRange.VersionRange?.ToString() ?? "", - ResolvedVersion = library.Version?.ToString(), + ResolvedVersion = lockFileTargetLibrary.Version?.ToString(), TargetFramework = tfm, IsTopLevel = isTopLevel, - IsAutoReferenced = dependency?.AutoReferenced ?? false + IsAutoReferenced = dependency?.AutoReferenced ?? false, + DependentPackages = mappedDependents }); } } diff --git a/src/SharpIDE.Application/Features/Nuget/NugetClientService.cs b/src/SharpIDE.Application/Features/Nuget/NugetClientService.cs index 00dc1f5..4a82d7e 100644 --- a/src/SharpIDE.Application/Features/Nuget/NugetClientService.cs +++ b/src/SharpIDE.Application/Features/Nuget/NugetClientService.cs @@ -9,7 +9,7 @@ using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; namespace SharpIDE.Application.Features.Nuget; -public record InstalledNugetPackageInfo(bool IsTransitive, NuGetVersion Version); +public record InstalledNugetPackageInfo(bool IsTransitive, NuGetVersion Version, List? DependentPackages); public record IdePackageResult(string PackageId, List PackageFromSources, InstalledNugetPackageInfo? InstalledNugetPackageInfo); public record struct IdePackageFromSourceResult(IPackageSearchMetadata PackageSearchMetadata, PackageSource Source); public class NugetClientService @@ -122,7 +122,7 @@ public class NugetClientService var nugetVersionString = installedPackage.ResolvedVersion ?? installedPackage.RequestedVersion; var nugetVersion = NuGetVersion.Parse(nugetVersionString); - var installedNugetPackageInfo = new InstalledNugetPackageInfo(isTransitive, nugetVersion); + var installedNugetPackageInfo = new InstalledNugetPackageInfo(isTransitive, nugetVersion, installedPackage.DependentPackages); var idePackageResult = new IdePackageResult(installedPackage.Name, [], installedNugetPackageInfo); foreach (var source in packageSources) diff --git a/src/SharpIDE.Godot/Features/Nuget/PackageEntry.cs b/src/SharpIDE.Godot/Features/Nuget/PackageEntry.cs index b7f6417..21501ff 100644 --- a/src/SharpIDE.Godot/Features/Nuget/PackageEntry.cs +++ b/src/SharpIDE.Godot/Features/Nuget/PackageEntry.cs @@ -53,6 +53,21 @@ public partial class PackageEntry : MarginContainer if (PackageResult is null) return; _packageNameLabel.Text = PackageResult.PackageId; var installedPackagedInfo = PackageResult.InstalledNugetPackageInfo; + if (installedPackagedInfo?.DependentPackages is not null) + { + var transitiveOriginsGroupedByVersion = installedPackagedInfo.DependentPackages + .GroupBy(t => t.RequestedVersion) + .Select(g => new + { + RequestedVersion = g.Key, + PackageNames = g.Select(t => t.PackageName).ToList() + }) + .ToList(); + _button.TooltipText = $""" + Implicitly Referenced Versions + {string.Join("\n", transitiveOriginsGroupedByVersion.Select(t => $"{t.RequestedVersion} by {string.Join(", ", t.PackageNames)}"))} + """; + } _installedVersionLabel.Text = installedPackagedInfo?.IsTransitive is true ? $"({installedPackagedInfo?.Version.ToNormalizedString()})" : installedPackagedInfo?.Version.ToNormalizedString(); var highestVersionPackageFromSource = PackageResult.PackageFromSources .MaxBy(p => p.PackageSearchMetadata.Identity.Version);