Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements #16

Merged
merged 4 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 21 additions & 15 deletions src/Models/NugetSession.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.IO;
using Community.VisualStudio.Toolkit;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.VisualStudio.Shell;
using NuGet.Configuration;
using NuGet.Protocol.Core.Types;
using Settings = NuGet.Configuration.Settings;
Expand All @@ -10,20 +11,37 @@ namespace NuGetMonitor.Models
internal sealed class NuGetSession : IDisposable
{
private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly TaskCompletionSource<ICollection<SourceRepository>> _sourceRepositories = new();

public NuGetSession()
{
Load();
ThreadHelper.ThrowIfNotOnUIThread();

var solution = VS.Solutions.GetCurrentSolution();
var solutionDirectory = Path.GetDirectoryName(solution?.FullPath);

var settings = Settings.LoadDefaultSettings(solutionDirectory);

GlobalPackagesFolder = SettingsUtility.GetGlobalPackagesFolder(settings);

var packageSourceProvider = new PackageSourceProvider(settings);
var sourceRepositoryProvider = new SourceRepositoryProvider(packageSourceProvider, Repository.Provider.GetCoreV3());
var sourceRepositories = sourceRepositoryProvider.GetRepositories();

SourceRepositories = sourceRepositories.ToArray();
PackageDownloadContext = new PackageDownloadContext(SourceCacheContext);
}

public MemoryCache Cache { get; } = new(new MemoryCacheOptions { });

public SourceCacheContext SourceCacheContext { get; } = new();

public PackageDownloadContext PackageDownloadContext { get; }

public CancellationToken CancellationToken => _cancellationTokenSource.Token;

public Task<ICollection<SourceRepository>> GetSourceRepositories() => _sourceRepositories.Task;
public ICollection<SourceRepository> SourceRepositories { get; }

public string GlobalPackagesFolder { get; private set; }

public void ThrowIfCancellationRequested() => CancellationToken.ThrowIfCancellationRequested();

Expand All @@ -34,17 +52,5 @@ public void Dispose()
SourceCacheContext.Dispose();
Cache.Dispose();
}

private async void Load()
{
var solution = await VS.Solutions.GetCurrentSolutionAsync();
var solutionDirectory = Path.GetDirectoryName(solution?.FullPath);

var packageSourceProvider = new PackageSourceProvider(Settings.LoadDefaultSettings(solutionDirectory));
var sourceRepositoryProvider = new SourceRepositoryProvider(packageSourceProvider, Repository.Provider.GetCoreV3());
var sourceRepositories = sourceRepositoryProvider.GetRepositories();

_sourceRepositories.SetResult(sourceRepositories.ToArray());
}
}
}
4 changes: 2 additions & 2 deletions src/Options/General.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

namespace NuGetMonitor.Options;

internal partial class OptionsProvider
internal sealed class OptionsProvider
{
[ComVisible(true)]
public class GeneralOptions : BaseOptionPage<General> { }
}

public class General : BaseOptionModel<General>
public sealed class General : BaseOptionModel<General>
{
[Category("Notifications")]
[DisplayName("Show transitive packages issues")]
Expand Down
47 changes: 20 additions & 27 deletions src/Services/NuGetService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.IO;
using System.Security.Principal;
using Microsoft.Extensions.Caching.Memory;
using NuGet.Common;
using NuGet.Frameworks;
Expand Down Expand Up @@ -84,19 +83,14 @@ public static async Task<ICollection<TransitiveDependencies>> GetTransitivePacka
{
var project = projectPackageReferences.Key;

var targetFrameworks = project.GetTargetFrameworks();
if (targetFrameworks is null)
{
await LoggingService.LogAsync($"No target framework found in project {Path.GetFileName(project.FullPath)} (old project format?) - skipping transitive package analysis.");
continue;
}
var projectsInTargetFramework = project.GetProjectsInTargetFramework();

var topLevelPackagesInProject = topLevelPackages
.Where(package => projectPackageReferences.Any(item => package.PackageReferenceEntries.Contains(item)))
.Select(item => item.PackageInfo)
.ToArray();

foreach (var targetFramework in targetFrameworks)
foreach (var projectInTargetFramework in projectsInTargetFramework)
{
var inputQueue = new Queue<PackageInfo>(topLevelPackagesInProject);
var parentsByChild = new Dictionary<PackageInfo, HashSet<PackageInfo>>();
Expand All @@ -113,7 +107,7 @@ public static async Task<ICollection<TransitiveDependencies>> GetTransitivePacka

processedItemsByPackageId[packageIdentity.Id] = packageInfo;

var dependencies = await packageInfo.GetPackageDependenciesInFramework(targetFramework);
var dependencies = await packageInfo.GetPackageDependenciesInFramework(projectInTargetFramework.TargetFramework);

foreach (var dependency in dependencies)
{
Expand All @@ -131,7 +125,7 @@ public static async Task<ICollection<TransitiveDependencies>> GetTransitivePacka
.Where(item => transitivePackages.Contains(item.Key))
.ToDictionary();

results.Add(new TransitiveDependencies(project, targetFramework, parentsByChild));
results.Add(new TransitiveDependencies(projectInTargetFramework.Project, projectInTargetFramework.TargetFramework, parentsByChild));
}
}

Expand Down Expand Up @@ -229,8 +223,7 @@ PackageDependenciesInFrameworkCacheEntry Factory(ICacheEntry cacheEntry)

private static bool IsOutdated(PackageIdentity packageIdentity, IEnumerable<NuGetVersion> versions)
{
var latestVersion =
versions.FirstOrDefault(version => version.IsPrerelease == packageIdentity.Version.IsPrerelease);
var latestVersion = versions.FirstOrDefault(version => version.IsPrerelease == packageIdentity.Version.IsPrerelease);

return latestVersion > packageIdentity.Version;
}
Expand Down Expand Up @@ -286,15 +279,20 @@ public PackageCacheEntry(string packageId, NuGetSession session)

private static async Task<Package?> GetPackage(string packageId, NuGetSession session)
{
foreach (var sourceRepository in await session.GetSourceRepositories())
foreach (var sourceRepository in session.SourceRepositories)
{
var packageResource = await sourceRepository.GetResourceAsync<FindPackageByIdResource>(session.CancellationToken);
var dependencyInfoResource = await sourceRepository.GetResourceAsync<DependencyInfoResource>(session.CancellationToken);

var resolvePackages = await dependencyInfoResource.ResolvePackages(packageId, session.SourceCacheContext, NullLogger.Instance, session.CancellationToken);

var unsortedVersions = await packageResource.GetAllVersionsAsync(packageId, session.SourceCacheContext, NullLogger.Instance, session.CancellationToken);
var unsortedVersions = resolvePackages
.Where(item => item.Listed)
.Select(item => item.Identity.Version)
.ToArray();

var versions = unsortedVersions?.OrderByDescending(item => item).ToArray();
var versions = unsortedVersions.OrderByDescending(item => item).ToArray();

if (versions?.Length > 0)
if (versions.Length > 0)
{
return new Package(packageId, versions, sourceRepository);
}
Expand Down Expand Up @@ -350,21 +348,16 @@ public PackageDependenciesCacheEntry(PackageIdentity packageIdentity, SourceRepo
if (string.Equals(packageIdentity.Id, "NETStandard.Library", StringComparison.OrdinalIgnoreCase))
return Array.Empty<PackageDependencyGroup>();

var resource = await repository.GetResourceAsync<FindPackageByIdResource>();
var resource = await repository.GetResourceAsync<DownloadResource>(session.CancellationToken);

var packageStream = new MemoryStream();
await resource.CopyNupkgToStreamAsync(packageIdentity.Id, packageIdentity.Version, packageStream, session.SourceCacheContext, NullLogger.Instance, session.CancellationToken);
using var downloadResult = await resource.GetDownloadResourceResultAsync(packageIdentity, session.PackageDownloadContext, session.GlobalPackagesFolder, NullLogger.Instance, session.CancellationToken);

if (packageStream.Length == 0)
if (downloadResult.Status != DownloadResourceResultStatus.Available)
return Array.Empty<PackageDependencyGroup>();

packageStream.Position = 0;

using var package = new PackageArchiveReader(packageStream);

var dependencyGroups = package.GetPackageDependencies().ToArray();
var dependencyGroups = await downloadResult.PackageReader.GetPackageDependenciesAsync(session.CancellationToken);

return dependencyGroups;
return dependencyGroups.ToArray();
}
}

Expand Down
51 changes: 43 additions & 8 deletions src/Services/ProjectService.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
using System.IO;
using Community.VisualStudio.Toolkit;
using EnvDTE;
using Microsoft.Build.Evaluation;
using NuGet.Frameworks;
using NuGet.Versioning;
using NuGetMonitor.Models;
using TomsToolbox.Essentials;
using Project = Microsoft.Build.Evaluation.Project;
using ProjectItem = Microsoft.Build.Evaluation.ProjectItem;

namespace NuGetMonitor.Services;

internal sealed record ProjectInTargetFramework(Project Project, NuGetFramework TargetFramework);

internal static class ProjectService
{
private static ProjectCollection _projectCollection = new();
Expand Down Expand Up @@ -45,20 +49,45 @@ public static async Task<IReadOnlyCollection<PackageReferenceEntry>> GetPackageR
});
}


public static NuGetFramework[]? GetTargetFrameworks(this Project project)
public static ProjectInTargetFramework[] GetProjectsInTargetFramework(this Project project)
{
var frameworkNames = (project.GetProperty("TargetFrameworks") ?? project.GetProperty("TargetFramework"))
?.EvaluatedValue
?.Split(';')
.Select(value => value.Trim());
.Select(value => value.Trim())
.ToArray();

var frameworks = frameworkNames?
if (frameworkNames is null || frameworkNames.Length == 0)
return new[] { new ProjectInTargetFramework(project, NuGetFramework.AgnosticFramework) };

var frameworks = frameworkNames
.Select(NuGetFramework.Parse)
.Distinct()
.ToArray();

return frameworks;
if (frameworks.Length == 1)
return new[] { new ProjectInTargetFramework(project, frameworks[0]) };

var projectCollection = _projectCollection;

lock (projectCollection)
{
return frameworks
.Select(framework => LoadProjectInTargetFramework(project, framework, projectCollection))
.ToArray();
}
}

private static ProjectInTargetFramework LoadProjectInTargetFramework(Project project, NuGetFramework framework, ProjectCollection projectCollection)
{
var properties = new Dictionary<string, string>
{
{ "TargetFramework", framework.GetShortFolderName() }
};

var specificProject = projectCollection.LoadProject(project.FullPath, properties, null);

return new ProjectInTargetFramework(specificProject, framework);
}

private static IEnumerable<PackageReferenceEntry> GetPackageReferences(ProjectCollection projectCollection, string projectPath)
Expand All @@ -76,12 +105,18 @@ private static IEnumerable<ProjectItem> GetPackageReferenceItems(ProjectCollecti
{
try
{
Project project;

lock (projectCollection)
{
var project = projectCollection.LoadProject(projectPath);

return project.AllEvaluatedItems;
project = projectCollection.LoadProject(projectPath);
}

var projects = project.GetProjectsInTargetFramework();

var allItems = projects.SelectMany(p => p.Project.GetItems("PackageReference"));

return allItems;
}
catch (Exception ex)
{
Expand Down