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

Feature/improvements #17

Merged
merged 2 commits into from
Sep 23, 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
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