Skip to content

Commit

Permalink
Merge pull request #17 from tom-englert/feature/improvements
Browse files Browse the repository at this point in the history
Feature/improvements
  • Loading branch information
sboulema authored Sep 23, 2023
2 parents 2f2d811 + 0e50747 commit 16d41e6
Show file tree
Hide file tree
Showing 15 changed files with 100 additions and 67 deletions.
7 changes: 7 additions & 0 deletions src/GlobalConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace NuGetMonitor
{
internal static class GlobalConstants
{
public static readonly string NetStandardPackageId = "NETStandard.Library";
}
}
4 changes: 4 additions & 0 deletions src/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
global using static Microsoft.VisualStudio.Shell.ThreadHelper;
global using static NuGetMonitor.GlobalConstants;
global using static NuGetMonitor.Services.LoggingService;

5 changes: 2 additions & 3 deletions src/Models/NugetSession.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
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 @@ -14,7 +13,7 @@ internal sealed class NuGetSession : IDisposable

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

var solution = VS.Solutions.GetCurrentSolution();
var solutionDirectory = Path.GetDirectoryName(solution?.FullPath);
Expand All @@ -31,7 +30,7 @@ public NuGetSession()
PackageDownloadContext = new PackageDownloadContext(SourceCacheContext);
}

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

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

Expand Down
12 changes: 6 additions & 6 deletions src/Models/PackageReferenceEntry.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
using Microsoft.Build.Evaluation;
using NuGet.Versioning;
using NuGet.Versioning;
using NuGetMonitor.Services;

namespace NuGetMonitor.Models;

internal sealed record PackageReferenceEntry
{
public PackageReferenceEntry(string id, VersionRange versionRange, ProjectItem projectItem, string justification)
public PackageReferenceEntry(string id, VersionRange versionRange, ProjectItemInTargetFramework projectItemInTargetFramework, string justification)
{
ProjectItem = projectItem;
ProjectItemInTargetFramework = projectItemInTargetFramework;
Justification = justification;
Identity = new PackageReference(id, versionRange);
}

public PackageReference Identity { get; }

public ProjectItem ProjectItem { get; }
public ProjectItemInTargetFramework ProjectItemInTargetFramework { get; }

public string Justification { get; }
};
}
10 changes: 5 additions & 5 deletions src/NuGetMonitorPackage.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using Community.VisualStudio.Toolkit;
using System.Runtime.InteropServices;
using Community.VisualStudio.Toolkit;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using NuGetMonitor.Options;
using NuGetMonitor.Services;
using NuGetMonitor.View;
using System.Runtime.InteropServices;
using Task = System.Threading.Tasks.Task;

namespace NuGetMonitor;

Expand All @@ -13,8 +13,8 @@ namespace NuGetMonitor;
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExistsAndFullyLoaded_string, PackageAutoLoadFlags.BackgroundLoad)]
[ProvideMenuResource("Menus.ctmenu", 1)]
[ProvideToolWindow(typeof(NuGetMonitorToolWindow))]
[ProvideOptionPage(typeof(Options.OptionsProvider.GeneralOptions), "NuGet Monitor", "General", 0, 0, true)]
[ProvideProfile(typeof(Options.OptionsProvider.GeneralOptions), "NuGet Monitor", "General", 0, 0, true)]
[ProvideOptionPage(typeof(OptionsProvider.GeneralOptions), "NuGet Monitor", "General", 0, 0, true)]
[ProvideProfile(typeof(OptionsProvider.GeneralOptions), "NuGet Monitor", "General", 0, 0, true)]
public sealed class NuGetMonitorPackage : ToolkitPackage
{
public const string PackageGuidString = "38279e01-6b27-4a29-9221-c4ea8748f16e";
Expand Down
4 changes: 2 additions & 2 deletions src/Options/General.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Community.VisualStudio.Toolkit;
using System.ComponentModel;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Community.VisualStudio.Toolkit;

namespace NuGetMonitor.Options;

Expand Down
8 changes: 3 additions & 5 deletions src/Services/InfoBarService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@
using System.Windows;
using Community.VisualStudio.Toolkit;
using Microsoft.VisualStudio.Imaging;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using NuGet.Versioning;
using NuGetMonitor.Models;
using NuGetMonitor.Options;
using NuGetMonitor.View;
using TomsToolbox.Essentials;

using static NuGetMonitor.Services.LoggingService;
using NuGetMonitor.Options;

namespace NuGetMonitor.Services;

internal static class InfoBarService
Expand Down Expand Up @@ -114,7 +112,7 @@ public static void CloseInfoBars()

private static void InfoBar_ActionItemClicked(object sender, InfoBarActionItemEventArgs e)
{
ThreadHelper.ThrowIfNotOnUIThread();
ThrowIfNotOnUIThread();

switch (e.ActionItem.ActionContext)
{
Expand Down
26 changes: 19 additions & 7 deletions src/Services/LoggingService.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
using Community.VisualStudio.Toolkit;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;

namespace NuGetMonitor.Services;

internal static class LoggingService
{
private static Guid _outputPaneGuid = new("{5B951352-356E-45A9-8F73-80DF1C57FED4}");

private static IVsOutputWindowPane? _outputWindowPane;

public static void Log(string message)
{
LogAsync(message).FireAndForget();
}

public static async Task LogAsync(string message)
{
_outputWindowPane ??= await GetOutputWindowPane();

await JoinableTaskFactory.SwitchToMainThreadAsync();

_outputWindowPane?.OutputStringThreadSafe($"[{DateTime.Now:T}] {message}\r\n");
}

private static async Task<IVsOutputWindowPane?> GetOutputWindowPane()
{
var outputWindow = await VS.Services.GetOutputWindowAsync();

await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
await JoinableTaskFactory.SwitchToMainThreadAsync();

var errorCode = outputWindow.GetPane(ref _outputPaneGuid, out var pane);

if (ErrorHandler.Failed(errorCode) || pane == null)
{
outputWindow.CreatePane(ref _outputPaneGuid, "NuGet Monitor", Convert.ToInt32(true), Convert.ToInt32(false));
outputWindow.GetPane(ref _outputPaneGuid, out pane);
}
if (!ErrorHandler.Failed(errorCode) && pane != null)
return pane;

outputWindow.CreatePane(ref _outputPaneGuid, "NuGet Monitor", Convert.ToInt32(true), Convert.ToInt32(false));
outputWindow.GetPane(ref _outputPaneGuid, out pane);

pane?.OutputStringThreadSafe($"[{DateTime.Now:T}] {message}\r\n");
return pane;
}
}
10 changes: 5 additions & 5 deletions src/Services/MonitorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,30 @@ private static async Task CheckForUpdatesInternal()
if (solution is null)
return;

await LoggingService.LogAsync($"Solution: {solution.Name}").ConfigureAwait(true);
await LogAsync($"Solution: {solution.Name}").ConfigureAwait(true);

await LoggingService.LogAsync("Check top level packages").ConfigureAwait(true);
await LogAsync("Check top level packages").ConfigureAwait(true);

var packageReferences = await ProjectService.GetPackageReferences().ConfigureAwait(true);

var topLevelPackages = await NuGetService.CheckPackageReferences(packageReferences).ConfigureAwait(true);

await LoggingService.LogAsync($"{topLevelPackages.Count} packages found").ConfigureAwait(true);
await LogAsync($"{topLevelPackages.Count} packages found").ConfigureAwait(true);

if (topLevelPackages.Count == 0)
return;

InfoBarService.ShowTopLevelPackageIssues(topLevelPackages);

await LoggingService.LogAsync("Check transitive packages").ConfigureAwait(true);
await LogAsync("Check transitive packages").ConfigureAwait(true);

var transitiveDependencies = await NuGetService.GetTransitivePackages(packageReferences, topLevelPackages).ConfigureAwait(true);

InfoBarService.ShowTransitivePackageIssues(transitiveDependencies);
}
catch (Exception ex) when (ex is not (OperationCanceledException or ObjectDisposedException))
{
await LoggingService.LogAsync($"Check for updates failed: {ex}");
await LogAsync($"Check for updates failed: {ex}");
}
}
}
32 changes: 17 additions & 15 deletions src/Services/NuGetService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.IO;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Caching.Memory;
using NuGet.Common;
using NuGet.Frameworks;
using NuGet.Packaging;
Expand Down Expand Up @@ -77,21 +76,23 @@ public static async Task<ICollection<TransitiveDependencies>> GetTransitivePacka
{
var results = new List<TransitiveDependencies>();

var packagesReferencesByProject = packageReferences.GroupBy(item => item.ProjectItem.Project);
var projectItemsByTargetFramework = packageReferences.GroupBy(item => item.ProjectItemInTargetFramework.TargetFramework);

foreach (var projectPackageReferences in packagesReferencesByProject)
foreach (var projectItemsInTargetFramework in projectItemsByTargetFramework)
{
var project = projectPackageReferences.Key;
var targetFramework = projectItemsInTargetFramework.Key;

var projectsInTargetFramework = project.GetProjectsInTargetFramework();
var packagesReferencesByProject = projectItemsInTargetFramework.GroupBy(item => item.ProjectItemInTargetFramework.ProjectItem.Project);

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

foreach (var projectInTargetFramework in projectsInTargetFramework)
foreach (var projectPackageReferences in packagesReferencesByProject)
{
var project = projectPackageReferences.Key;

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

var inputQueue = new Queue<PackageInfo>(topLevelPackagesInProject);
var parentsByChild = new Dictionary<PackageInfo, HashSet<PackageInfo>>();
var processedItemsByPackageId = new Dictionary<string, PackageInfo>();
Expand All @@ -107,7 +108,7 @@ public static async Task<ICollection<TransitiveDependencies>> GetTransitivePacka

processedItemsByPackageId[packageIdentity.Id] = packageInfo;

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

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

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

Expand Down Expand Up @@ -345,7 +346,8 @@ public PackageDependenciesCacheEntry(PackageIdentity packageIdentity, SourceRepo
private static async Task<PackageDependencyGroup[]?> GetDirectDependencies(PackageIdentity packageIdentity, SourceRepository repository, NuGetSession session)
{
// Don't scan packages with pseudo-references, they don't get physically included, but cause vulnerability warnings.
if (string.Equals(packageIdentity.Id, "NETStandard.Library", StringComparison.OrdinalIgnoreCase))

if (string.Equals(packageIdentity.Id, NetStandardPackageId, StringComparison.OrdinalIgnoreCase))
return Array.Empty<PackageDependencyGroup>();

var resource = await repository.GetResourceAsync<DownloadResource>(session.CancellationToken);
Expand Down
32 changes: 22 additions & 10 deletions src/Services/ProjectService.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
using System.IO;
using Community.VisualStudio.Toolkit;
using EnvDTE;
using Microsoft.Build.Construction;
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 ProjectItemInTargetFramework(ProjectItem ProjectItem, NuGetFramework TargetFramework)
{
public ProjectRootElement ContainingProject => ProjectItem.Xml.ContainingProject;
}

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

internal static class ProjectService
Expand Down Expand Up @@ -44,7 +48,7 @@ public static async Task<IReadOnlyCollection<PackageReferenceEntry>> GetPackageR
return references
.SelectMany(items => items)
.OrderBy(item => item.Identity.Id)
.ThenBy(item => Path.GetFileName(item.ProjectItem.Xml.ContainingProject.FullPath))
.ThenBy(item => Path.GetFileName(item.ProjectItemInTargetFramework.ContainingProject.FullPath))
.ToArray();
});
}
Expand All @@ -53,7 +57,7 @@ public static ProjectInTargetFramework[] GetProjectsInTargetFramework(this Proje
{
var frameworkNames = (project.GetProperty("TargetFrameworks") ?? project.GetProperty("TargetFramework"))
?.EvaluatedValue
?.Split(';')
.Split(';')
.Select(value => value.Trim())
.ToArray();

Expand Down Expand Up @@ -101,7 +105,7 @@ private static IEnumerable<PackageReferenceEntry> GetPackageReferences(ProjectCo
return packageReferences;
}

private static IEnumerable<ProjectItem> GetPackageReferenceItems(ProjectCollection projectCollection, string projectPath)
private static IEnumerable<ProjectItemInTargetFramework> GetPackageReferenceItems(ProjectCollection projectCollection, string projectPath)
{
try
{
Expand All @@ -114,27 +118,35 @@ private static IEnumerable<ProjectItem> GetPackageReferenceItems(ProjectCollecti

var projects = project.GetProjectsInTargetFramework();

var allItems = projects.SelectMany(p => p.Project.GetItems("PackageReference"));
var allItems = projects.SelectMany(p => p.Project.GetItems("PackageReference").Select(item => new ProjectItemInTargetFramework(item, p.TargetFramework)));

return allItems;
}
catch (Exception ex)
{
LoggingService.Log($"Get package reference item failed: {ex}");
Log($"Get package reference item failed: {ex}");

return Enumerable.Empty<ProjectItem>();
return Enumerable.Empty<ProjectItemInTargetFramework>();
}
}

private static PackageReferenceEntry? CreateEntry(ProjectItem projectItem)
private static PackageReferenceEntry? CreateEntry(ProjectItemInTargetFramework projectItemInTargetFramework)
{
var projectItem = projectItemInTargetFramework.ProjectItem;

var id = projectItem.EvaluatedInclude;

// Ignore the implicit NetStandard library reference in projects targeting NetStandard.
if (id.Equals(NetStandardPackageId, StringComparison.OrdinalIgnoreCase))
return null;

var versionValue = projectItem.GetMetadata("Version")?.EvaluatedValue;
if (versionValue.IsNullOrEmpty())
return null;

return VersionRange.TryParse(versionValue, out var versionRange)
? new PackageReferenceEntry(id, versionRange, projectItem, projectItem.GetMetadataValue("Justification"))
? new PackageReferenceEntry(id, versionRange, projectItemInTargetFramework, projectItem.GetMetadataValue("Justification"))
: null;

}
}
7 changes: 3 additions & 4 deletions src/View/NuGetMonitorCommand.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Microsoft.VisualStudio.Shell;
using System.ComponentModel.Design;
using Task = System.Threading.Tasks.Task;
using System.ComponentModel.Design;
using Microsoft.VisualStudio.Shell;

namespace NuGetMonitor.View;

Expand Down Expand Up @@ -30,7 +29,7 @@ public static NuGetMonitorCommand? Instance

public static async Task InitializeAsync(AsyncPackage package)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
await JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);

var commandService = await package.GetServiceAsync(typeof(IMenuCommandService)).ConfigureAwait(true) as OleMenuCommandService ?? throw new InvalidOperationException("Failed to get menu command service");

Expand Down
Loading

0 comments on commit 16d41e6

Please sign in to comment.