Skip to content

Commit

Permalink
Rewrite dependencies tree
Browse files Browse the repository at this point in the history
This rewrite brings the dependencies tree in line with the current best practices for modelling project data. Specifically:

- Using standard dataflow blocks for merging data across configurations (replacing the previous `AggregateCrossTargetProjectContext` and related types).
- Using "slices" to simplify data handling and correctly support switching the active configuration.
- Making a snapshot of dependencies available at various levels, simplifying certain behaviours (previously we'd only have deltas, so components needed to track their own state independently).

The dependencies tree now populates much sooner, and no longer shows yellow triangles during project load for projects that are already restored, which greatly improves perceived performance. Fewer tree updates also improve actual performance.

A simpler representation of dependencies is now made. Instead of `IDependencyModel` which had many properties, we now have `IDependency` with only a few. If a dependency exposes a browse object, it may also implement `IDependencyWithBrowseObject` which adds a few extra properties. If we expose additional capabilities in future, we can add more interfaces to support that, which providers may opt in to.

The rewrite introduces the distinction between "configured" and "unconfigured" dependencies. For example, all dependencies coming from MSBuild are configured, while for a TypeScript/JavaScript project NPM packages are unconfigured (they have no concept of MSBuild configurations). Unconfigured dependencies are displayed at the top level, while configured dependencies are displayed beneath a node reflecting their configuration (when multi-targeting).

We also introduce an abstraction for MSBuild properties that come from project/joint rule sources. There is no common representation for dependencies in MSBuild, so a "factory" abstraction is introduced to take care of mapping between items and their metadata to a common representation.

A new `DependencyGroupType` type was added to model information common to all dependencies of a given type.

Other miscellaneous changes in this rewrite:

- Remove all explicit handling of target frameworks from the code. Everything is now performed in terms of "slices" which include target frameworks and potentially other implicit configuration dimensions.
- Tree building now occurs directly on `IDependency`, removing the need for the additional `IDependencyViewModel` object and the copying associated with that.
- Improve tree update logic to avoid collapsing expanded nodes during updates.
- Move all "legacy" extensibility code into its own folder, and implement in terms of the new APIs in `LegacyDependencySubscriber`.
- Added static class `KnownProjectImageMonikers` which caches `ProjectImageMoniker` instances, avoiding the locking and work done within `ToProjectSystemType()` on each use of an image moniker at run time.
- Consolidation of namespaces. Originally the dependencies tree was in the VS layer. In 16.x much of the code was moved to the host agnostic layer, however namespaces could not be updated. This rewrite moves us towards addressing that, where possible, by replacing the previous.
  • Loading branch information
drewnoakes committed May 10, 2023
1 parent ac586f4 commit efdbada
Show file tree
Hide file tree
Showing 136 changed files with 5,456 additions and 9,099 deletions.
289 changes: 139 additions & 150 deletions docs/repo/dependencies-node-roadmap.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

using Microsoft.VisualStudio.IO;
using Microsoft.VisualStudio.ProjectSystem.Input;
using Microsoft.VisualStudio.ProjectSystem.VS.Tree.Dependencies;
using Microsoft.VisualStudio.ProjectSystem.Tree.Dependencies;

namespace Microsoft.VisualStudio.ProjectSystem.VS.Input.Commands
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

using Microsoft.VisualStudio.IO;
using Microsoft.VisualStudio.ProjectSystem.Input;
using Microsoft.VisualStudio.ProjectSystem.VS.Tree.Dependencies;
using Microsoft.VisualStudio.ProjectSystem.Tree.Dependencies;

namespace Microsoft.VisualStudio.ProjectSystem.VS.Input.Commands
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using Microsoft.VisualStudio.ProjectSystem.Utilities;
using Microsoft.VisualStudio.Shell;

using Flags = Microsoft.VisualStudio.ProjectSystem.Tree.Dependencies.DependencyTreeFlags;

namespace Microsoft.VisualStudio.ProjectSystem.VS.Tree.Dependencies.AttachedCollections
{
/// <summary>
Expand Down Expand Up @@ -44,7 +46,7 @@ public DependenciesTreeProjectSearchContext(

IProjectTree targetRootNode;

if (_dependenciesNode.FindChildWithFlags(DependencyTreeFlags.TargetNode) is null)
if (_dependenciesNode.FindChildWithFlags(Flags.TargetNode) is null)
{
// Tree does not show any target nodes
targetRootNode = _dependenciesNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.Utilities;

using Flags = Microsoft.VisualStudio.ProjectSystem.Tree.Dependencies.DependencyTreeFlags;

namespace Microsoft.VisualStudio.ProjectSystem.VS.Tree.Dependencies.AttachedCollections
{
/// <summary>
Expand Down Expand Up @@ -95,7 +97,7 @@ Task SearchSolutionAsync()
async Task SearchProjectAsync(UnconfiguredProject unconfiguredProject)
{
IUnconfiguredProjectVsServices? projectVsServices = unconfiguredProject.Services.ExportProvider.GetExportedValue<IUnconfiguredProjectVsServices>();
IProjectTree? dependenciesNode = projectVsServices?.ProjectTree.CurrentTree?.FindChildWithFlags(DependencyTreeFlags.DependenciesRootNode);
IProjectTree? dependenciesNode = projectVsServices?.ProjectTree.CurrentTree?.FindChildWithFlags(Flags.DependenciesRootNode);

if (projectVsServices is not null && dependenciesNode is not null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;

using Flags = Microsoft.VisualStudio.ProjectSystem.Tree.Dependencies.DependencyTreeFlags;

namespace Microsoft.VisualStudio.ProjectSystem.VS.Tree.Dependencies.AttachedCollections
{
/// <summary>
Expand All @@ -24,7 +26,7 @@ internal static class IVsHierarchyItemExtensions
/// <returns><see langword="true"/> if the target was found, otherwise <see langword="false"/>.</returns>
public static bool TryFindTarget(this IVsHierarchyItem item, [NotNullWhen(returnValue: true)] out string? target)
{
s_targetFlagsRegex ??= new Regex(@"^(?=.*\b" + nameof(DependencyTreeFlags.TargetNode) + @"\b)(?=.*\$TFM:(?<target>[^ ]+)\b).*$", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant);
s_targetFlagsRegex ??= new Regex(@"^(?=.*\b" + nameof(Flags.TargetNode) + @"\b)(?=.*\$TFM:(?<target>[^ ]+)\b).*$", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant);

for (IVsHierarchyItem? parent = item; parent is not null; parent = parent.Parent)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Utilities;

using Flags = Microsoft.VisualStudio.ProjectSystem.Tree.Dependencies.DependencyTreeFlags;

namespace Microsoft.VisualStudio.ProjectSystem.VS.Tree.Dependencies.AttachedCollections.Implementation
{
[AppliesToProject(ProjectCapability.DependenciesTree)]
Expand All @@ -19,7 +21,7 @@ internal sealed class FrameworkReferenceAssemblyAttachedCollectionSourceProvider

[ImportingConstructor]
public FrameworkReferenceAssemblyAttachedCollectionSourceProvider(IRelationProvider relationProvider)
: base(DependencyTreeFlags.FrameworkDependency)
: base(Flags.FrameworkDependency)
{
_relationProvider = relationProvider;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.

using Flags = Microsoft.VisualStudio.ProjectSystem.Tree.Dependencies.DependencyTreeFlags;

namespace Microsoft.VisualStudio.ProjectSystem.VS.Tree.Dependencies.Commands
{
/// <summary>
Expand Down Expand Up @@ -35,71 +37,71 @@ public bool TryGetContextMenu(IProjectTree projectItem, out Guid menuCommandGuid
{
Requires.NotNull(projectItem);

if (projectItem.Flags.Contains(DependencyTreeFlags.DependenciesRootNode))
if (projectItem.Flags.Contains(Flags.DependenciesRootNode))
{
menuCommandId = Menus.IDM_VS_CTXT_REFERENCEROOT;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.TargetNode))
else if (projectItem.Flags.Contains(Flags.TargetNode))
{
menuCommandId = Menus.IDM_VS_CTXT_DEPENDENCYTARGET;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.AssemblyDependencyGroup))
else if (projectItem.Flags.Contains(Flags.AssemblyDependencyGroup))
{
menuCommandId = Menus.IDM_VS_CTXT_REFERENCE_GROUP;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.AssemblyDependency))
else if (projectItem.Flags.Contains(Flags.AssemblyDependency))
{
menuCommandId = Menus.IDM_VS_CTXT_REFERENCE;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.PackageDependencyGroup))
else if (projectItem.Flags.Contains(Flags.PackageDependencyGroup))
{
menuCommandId = Menus.IDM_VS_CTXT_PACKAGEREFERENCE_GROUP;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.PackageDependency))
else if (projectItem.Flags.Contains(Flags.PackageDependency))
{
menuCommandId = Menus.IDM_VS_CTXT_PACKAGEREFERENCE;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.ComDependencyGroup))
else if (projectItem.Flags.Contains(Flags.ComDependencyGroup))
{
menuCommandId = Menus.IDM_VS_CTXT_COMREFERENCE_GROUP;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.ComDependency))
else if (projectItem.Flags.Contains(Flags.ComDependency))
{
menuCommandId = Menus.IDM_VS_CTXT_COMREFERENCE;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.ProjectDependencyGroup))
else if (projectItem.Flags.Contains(Flags.ProjectDependencyGroup))
{
menuCommandId = Menus.IDM_VS_CTXT_PROJECTREFERENCE_GROUP;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.ProjectDependency))
else if (projectItem.Flags.Contains(Flags.ProjectDependency))
{
menuCommandId = Menus.IDM_VS_CTXT_PROJECTREFERENCE;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.SharedProjectDependency))
else if (projectItem.Flags.Contains(Flags.SharedProjectDependency))
{
menuCommandId = Menus.IDM_VS_CTXT_SHAREDPROJECTREFERENCE;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.AnalyzerDependencyGroup))
else if (projectItem.Flags.Contains(Flags.AnalyzerDependencyGroup))
{
menuCommandId = Menus.IDM_VS_CTXT_ANALYZERREFERENCE_GROUP;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.AnalyzerDependency))
else if (projectItem.Flags.Contains(Flags.AnalyzerDependency))
{
menuCommandId = Menus.IDM_VS_CTXT_ANALYZERREFERENCE;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.FrameworkDependencyGroup))
else if (projectItem.Flags.Contains(Flags.FrameworkDependencyGroup))
{
menuCommandId = Menus.IDM_VS_CTXT_FRAMEWORKREFERENCE_GROUP;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.FrameworkDependency))
else if (projectItem.Flags.Contains(Flags.FrameworkDependency))
{
menuCommandId = Menus.IDM_VS_CTXT_FRAMEWORKREFERENCE;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.SdkDependencyGroup))
else if (projectItem.Flags.Contains(Flags.SdkDependencyGroup))
{
menuCommandId = Menus.IDM_VS_CTXT_SDKREFERENCE_GROUP;
}
else if (projectItem.Flags.Contains(DependencyTreeFlags.SdkDependency))
else if (projectItem.Flags.Contains(Flags.SdkDependency))
{
menuCommandId = Menus.IDM_VS_CTXT_SDKREFERENCE;
}
Expand Down Expand Up @@ -132,9 +134,9 @@ public bool TryGetMixedItemsContextMenu(IEnumerable<IProjectTree> projectItems,

if (!containsProhibited)
{
if (item.Flags.Contains(DependencyTreeFlags.DependencyGroup) ||
item.Flags.Contains(DependencyTreeFlags.TargetNode) ||
item.Flags.Contains(DependencyTreeFlags.DependenciesRootNode))
if (item.Flags.Contains(Flags.DependencyGroup) ||
item.Flags.Contains(Flags.TargetNode) ||
item.Flags.Contains(Flags.DependenciesRootNode))
{
containsProhibited = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;

using Flags = Microsoft.VisualStudio.ProjectSystem.Tree.Dependencies.DependencyTreeFlags;

namespace Microsoft.VisualStudio.ProjectSystem.VS.Tree.Dependencies.Commands
{
/// <summary>
Expand Down Expand Up @@ -58,8 +60,8 @@ protected override async Task<bool> TryHandleCommandAsync(IProjectTree node, boo
private static bool CanNavigateTo(IProjectTree node)
{
return node.Flags.ContainsAny(
DependencyTreeFlags.ProjectDependency |
DependencyTreeFlags.SharedProjectDependency);
Flags.ProjectDependency |
Flags.SharedProjectDependency);
}

private async Task NavigateToAsync(IProjectTree node)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using Microsoft.VisualStudio.ProjectSystem.Input;
using Microsoft.VisualStudio.Threading;

using Flags = Microsoft.VisualStudio.ProjectSystem.Tree.Dependencies.DependencyTreeFlags;

namespace Microsoft.VisualStudio.ProjectSystem.VS.Tree.Dependencies.Commands
{
/// <summary>
Expand All @@ -15,7 +17,7 @@ internal sealed class SuppressObjectBrowserForPackageReferenceCommand : Abstract
{
protected override Task<CommandStatusResult> GetCommandStatusAsync(IProjectTree node, bool focused, string? commandText, CommandStatus progressiveStatus)
{
if (node.Flags.Contains(DependencyTreeFlags.PackageDependency))
if (node.Flags.Contains(Flags.PackageDependency))
{
return GetCommandStatusResult.Suppressed;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.VisualStudio.ProjectSystem.Tree.Dependencies;
using Microsoft.VisualStudio.ProjectSystem.VS.Tree.Dependencies;

namespace Microsoft.VisualStudio.ProjectSystem.CopyPaste
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,5 +232,33 @@ public static Task RegisterFaultHandlerAsync(
TaskContinuationOptions.OnlyOnFaulted,
TaskScheduler.Default);
}

/// <summary>
/// Attaches error handling to a block so that if it throws an unhandled exception,
/// the error will be reported to the user.
/// </summary>
/// <param name="faultHandlerService">
/// The <see cref="IProjectFaultHostHandler"/> that should handle the fault.
/// </param>
/// <param name="block">
/// The block to attach error handling to.
/// </param>
/// <param name="project">
/// The project related to the failure, if applicable. Can be <see langword="null"/>.
/// </param>
/// <param name="severity">
/// The severity of the failure.
/// </param>
public static void RegisterFaultHandler(
this IProjectFaultHandlerService faultHandlerService,
IDataflowBlock block,
UnconfiguredProject? project,
ProjectFaultSeverity severity = ProjectFaultSeverity.Recoverable)
{
Task task = RegisterFaultHandlerAsync(faultHandlerService, block, project, severity);

// We don't actually care about the result of reporting the fault if one occurs
faultHandlerService.Forget(task, project);
}
}
}

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit efdbada

Please sign in to comment.