private void ProcessSharedProjectsUpdates( IProjectSharedFoldersSnapshot sharedFolders, TargetFramework targetFramework, DependenciesChangesBuilder changesBuilder) { Requires.NotNull(sharedFolders, nameof(sharedFolders)); Requires.NotNull(targetFramework, nameof(targetFramework)); Requires.NotNull(changesBuilder, nameof(changesBuilder)); DependenciesSnapshot snapshot = _dependenciesSnapshotProvider.CurrentSnapshot; if (!snapshot.DependenciesByTargetFramework.TryGetValue(targetFramework, out TargetedDependenciesSnapshot? targetedSnapshot)) { return; } IEnumerable <string> sharedFolderProjectPaths = sharedFolders.Value.Select(sf => sf.ProjectPath); var currentSharedImportNodePaths = targetedSnapshot.Dependencies .Where(pair => pair.Flags.Contains(DependencyTreeFlags.SharedProjectDependency)) .Select(pair => pair.FilePath !) .ToList(); var diff = new SetDiff <string>(currentSharedImportNodePaths, sharedFolderProjectPaths); // process added nodes foreach (string addedSharedImportPath in diff.Added) { IDependencyModel added = new SharedProjectDependencyModel( addedSharedImportPath, addedSharedImportPath, isResolved: true, isImplicit: false, properties: ImmutableStringDictionary <string> .EmptyOrdinal); changesBuilder.Added(targetFramework, added); } // process removed nodes foreach (string removedSharedImportPath in diff.Removed) { bool exists = currentSharedImportNodePaths.Any(nodePath => PathHelper.IsSamePath(nodePath, removedSharedImportPath)); if (exists) { changesBuilder.Removed( targetFramework, ProjectRuleHandler.ProviderTypeString, dependencyId: removedSharedImportPath); } } }
public SnapshotUpdater(IProjectThreadingService projectThreadingService, CancellationToken unloadCancellationToken) { // Initial snapshot is empty. _currentSnapshot = DependenciesSnapshot.Empty; // Updates will be published via Dataflow. _source = DataflowBlockSlim.CreateBroadcastBlock <SnapshotChangedEventArgs>("DependenciesSnapshot {1}", skipIntermediateInputData: true); // Updates are debounced to conflate rapid updates and reduce frequency of tree updates downstream. _debounce = new TaskDelayScheduler( TimeSpan.FromMilliseconds(250), projectThreadingService, unloadCancellationToken); }
public DependenciesSnapshotProvider( IUnconfiguredProjectCommonServices commonServices, Lazy <IAggregateCrossTargetProjectContextProvider> contextProvider, IUnconfiguredProjectTasksService tasksService, IActiveConfiguredProjectSubscriptionService activeConfiguredProjectSubscriptionService, IActiveProjectConfigurationRefreshService activeProjectConfigurationRefreshService, ITargetFrameworkProvider targetFrameworkProvider, IAggregateDependenciesSnapshotProvider aggregateSnapshotProvider) : base(commonServices.ThreadingService.JoinableTaskContext) { Requires.NotNull(contextProvider, nameof(contextProvider)); Requires.NotNull(tasksService, nameof(tasksService)); Requires.NotNull(activeConfiguredProjectSubscriptionService, nameof(activeConfiguredProjectSubscriptionService)); Requires.NotNull(activeProjectConfigurationRefreshService, nameof(activeProjectConfigurationRefreshService)); Requires.NotNull(targetFrameworkProvider, nameof(targetFrameworkProvider)); Requires.NotNull(aggregateSnapshotProvider, nameof(aggregateSnapshotProvider)); _commonServices = commonServices; _contextProvider = contextProvider; _tasksService = tasksService; _activeConfiguredProjectSubscriptionService = activeConfiguredProjectSubscriptionService; _activeProjectConfigurationRefreshService = activeProjectConfigurationRefreshService; _targetFrameworkProvider = targetFrameworkProvider; _currentSnapshot = DependenciesSnapshot.CreateEmpty(_commonServices.Project.FullPath); _dependencySubscribers = new OrderPrecedenceImportCollection <IDependencyCrossTargetSubscriber>( projectCapabilityCheckProvider: commonServices.Project); _snapshotFilters = new OrderPrecedenceImportCollection <IDependenciesSnapshotFilter>( ImportOrderPrecedenceComparer.PreferenceOrder.PreferredComesLast, projectCapabilityCheckProvider: commonServices.Project); _subTreeProviders = new OrderPrecedenceImportCollection <IProjectDependenciesSubTreeProvider>( ImportOrderPrecedenceComparer.PreferenceOrder.PreferredComesLast, projectCapabilityCheckProvider: commonServices.Project); _dependenciesUpdateScheduler = new TaskDelayScheduler( _dependenciesUpdateThrottleInterval, commonServices.ThreadingService, tasksService.UnloadCancellationToken); _snapshotChangedSource = DataflowBlockSlim.CreateBroadcastBlock <SnapshotChangedEventArgs>("DependenciesSnapshot {1}", skipIntermediateInputData: true); aggregateSnapshotProvider.RegisterSnapshotProvider(this); }
/// <summary> /// Does flat search among dependency world lists to find any dependencies that match search criteria. /// </summary> private static HashSet <IDependency> SearchFlat(string searchTerm, DependenciesSnapshot dependenciesSnapshot) { var matchedDependencies = new HashSet <IDependency>(DependencyIdComparer.Instance); foreach ((ITargetFramework _, TargetedDependenciesSnapshot targetedSnapshot) in dependenciesSnapshot.DependenciesByTargetFramework) { foreach ((string _, IDependency dependency) in targetedSnapshot.DependenciesWorld) { if (dependency.Visible && dependency.Caption.IndexOf(searchTerm, StringComparison.CurrentCultureIgnoreCase) != -1) { matchedDependencies.Add(dependency); } } } return(matchedDependencies); }
public DependencySubscriptionsHost( IUnconfiguredProjectCommonServices commonServices, Lazy <IAggregateCrossTargetProjectContextProvider> contextProvider, [Import(ExportContractNames.Scopes.UnconfiguredProject)] IProjectAsynchronousTasksService tasksService, IActiveConfiguredProjectSubscriptionService activeConfiguredProjectSubscriptionService, IActiveProjectConfigurationRefreshService activeProjectConfigurationRefreshService, ITargetFrameworkProvider targetFrameworkProvider, IAggregateDependenciesSnapshotProvider aggregateSnapshotProvider) : base(commonServices.ThreadingService.JoinableTaskContext) { Requires.NotNull(contextProvider, nameof(contextProvider)); Requires.NotNull(tasksService, nameof(tasksService)); Requires.NotNull(activeConfiguredProjectSubscriptionService, nameof(activeConfiguredProjectSubscriptionService)); Requires.NotNull(activeProjectConfigurationRefreshService, nameof(activeProjectConfigurationRefreshService)); Requires.NotNull(targetFrameworkProvider, nameof(targetFrameworkProvider)); Requires.NotNull(aggregateSnapshotProvider, nameof(aggregateSnapshotProvider)); _commonServices = commonServices; _contextProvider = contextProvider; _tasksService = tasksService; _activeConfiguredProjectSubscriptionService = activeConfiguredProjectSubscriptionService; _activeProjectConfigurationRefreshService = activeProjectConfigurationRefreshService; _targetFrameworkProvider = targetFrameworkProvider; _aggregateSnapshotProvider = aggregateSnapshotProvider; _currentSnapshot = DependenciesSnapshot.CreateEmpty(_commonServices.Project.FullPath); _dependencySubscribers = new OrderPrecedenceImportCollection <IDependencyCrossTargetSubscriber>( projectCapabilityCheckProvider: commonServices.Project); _snapshotFilters = new OrderPrecedenceImportCollection <IDependenciesSnapshotFilter>( projectCapabilityCheckProvider: commonServices.Project, orderingStyle: ImportOrderPrecedenceComparer.PreferenceOrder.PreferredComesLast); _subTreeProviders = new OrderPrecedenceImportCollection <IProjectDependenciesSubTreeProvider>( ImportOrderPrecedenceComparer.PreferenceOrder.PreferredComesLast, projectCapabilityCheckProvider: commonServices.Project); _dependenciesUpdateScheduler = new TaskDelayScheduler( _dependenciesUpdateThrottleInterval, commonServices.ThreadingService, tasksService.UnloadCancellationToken); ProjectFilePath = commonServices.Project.FullPath; }
public async Task BuildTreeAsync_EmptySnapshot_CreatesRootNode() { // Arrange var dependenciesRoot = new TestProjectTree { Caption = "MyDependencies" }; var snapshot = DependenciesSnapshot.CreateEmpty(ProjectPath); // Act var resultTree = await CreateProvider().BuildTreeAsync(dependenciesRoot, snapshot); // Assert var expectedFlatHierarchy = "Caption=MyDependencies, FilePath=, IconHash=325248080, ExpandedIconHash=325248080, Rule=, IsProjectItem=False, CustomTag="; Assert.Equal(expectedFlatHierarchy, ToTestDataString((TestProjectTree)resultTree)); Assert.Equal(s_rootImage.ToProjectSystemType(), resultTree.Icon); Assert.Equal(s_rootImage.ToProjectSystemType(), resultTree.ExpandedIcon); }
protected override void OnAggregateContextChanged( AggregateCrossTargetProjectContext oldContext, AggregateCrossTargetProjectContext newContext) { if (oldContext == null) { // all new rules will be sent to new context , we don't need to clean up anything return; } var targetsToClean = new HashSet <ITargetFramework>(); IEnumerable <ITargetFramework> oldTargets = oldContext.InnerProjectContexts .Select(x => x.TargetFramework) .Where(x => x != null); if (newContext == null) { targetsToClean.AddRange(oldTargets); } else { IEnumerable <ITargetFramework> newTargets = newContext.InnerProjectContexts .Select(x => x.TargetFramework) .Where(x => x != null); targetsToClean.AddRange(oldTargets.Except(newTargets)); } if (targetsToClean.Count == 0) { return; } lock (_snapshotLock) { _currentSnapshot = _currentSnapshot.RemoveTargets(targetsToClean); } ScheduleDependenciesUpdate(); }
/// <summary> /// Executes <paramref name="updateFunc"/> on the current snapshot within a lock. If a new snapshot /// object is returned, <see cref="Current"/> is updated and the update is posted to <see cref="Source"/>. /// </summary> /// <returns>The updated snapshot, or <see langword="null" /> if no update occurred.</returns> public DependenciesSnapshot?TryUpdate(Func <DependenciesSnapshot, DependenciesSnapshot> updateFunc, CancellationToken token = default) { if (_isDisposed != 0) { throw new ObjectDisposedException(nameof(SnapshotUpdater)); } DependenciesSnapshot updatedSnapshot; lock (_lock) { updatedSnapshot = updateFunc(_currentSnapshot); if (ReferenceEquals(_currentSnapshot, updatedSnapshot)) { return(null); } _currentSnapshot = updatedSnapshot; } // Conflate rapid snapshot updates by debouncing events over a short window. // This reduces the frequency of tree updates with minimal perceived latency. _ = _debounce.ScheduleAsyncTask( ct => { if (ct.IsCancellationRequested || _isDisposed != 0) { return(Task.FromCanceled(ct)); } // Always publish the latest snapshot DependenciesSnapshot snapshot = _currentSnapshot; _source.Post(new SnapshotChangedEventArgs(snapshot, ct)); return(Task.CompletedTask); }, token); return(updatedSnapshot); }
private void UpdateDependenciesSnapshotAsync( ImmutableDictionary <ITargetFramework, IDependenciesChanges> changes, IProjectCatalogSnapshot catalogs, ITargetFramework activeTargetFramework) { DependenciesSnapshot newSnapshot; bool anyChanges = false; HashSet <string> projectItemSpecs = null; CommonServices.ThreadingService.JoinableTaskFactory.Run(async() => { projectItemSpecs = await ProjectXmlAccessor.GetProjectItems().ConfigureAwait(false); }); // Note: we are updating existing snapshot, not receivig a complete new one. Thus we must // ensure incremental updates are done in the correct order. This lock ensures that here. lock (_snapshotLock) { newSnapshot = DependenciesSnapshot.FromChanges( CommonServices.Project.FullPath, _currentSnapshot, changes, catalogs, activeTargetFramework, SnapshotFilters.Select(x => x.Value), SubTreeProviders.Select(x => x.Value), projectItemSpecs, out anyChanges); _currentSnapshot = newSnapshot; } if (anyChanges) { // avoid unnecessary tree updates ScheduleDependenciesUpdate(); } }
private void OnAggregateContextChanged( AggregateCrossTargetProjectContext oldContext, AggregateCrossTargetProjectContext newContext) { if (oldContext == null) { // all new rules will be sent to new context , we don't need to clean up anything return; } var targetsToClean = new HashSet <ITargetFramework>(); ImmutableArray <ITargetFramework> oldTargets = oldContext.TargetFrameworks; if (newContext == null) { targetsToClean.AddRange(oldTargets); } else { ImmutableArray <ITargetFramework> newTargets = newContext.TargetFrameworks; targetsToClean.AddRange(oldTargets.Except(newTargets)); } if (targetsToClean.Count == 0) { return; } lock (_snapshotLock) { _currentSnapshot = _currentSnapshot.RemoveTargets(targetsToClean); } ScheduleDependenciesUpdate(); }
/// <summary> /// Gets a value indicating whether deleting a given set of items from the project, and optionally from disk, /// would be allowed. /// Note: CanRemove can be called several times since there two types of remove operations: /// - Remove is a command that can remove project tree items form the tree/project but not from disk. /// For that command requests deleteOptions has DeleteOptions.None flag. /// - Delete is a command that can remove project tree items and form project and from disk. /// For this command requests deleteOptions has DeleteOptions.DeleteFromStorage flag. /// We can potentially support only Remove command here, since we don't remove Dependencies form disk, /// thus we return false when DeleteOptions.DeleteFromStorage is provided. /// </summary> /// <param name="nodes">The nodes that should be deleted.</param> /// <param name="deleteOptions"> /// A value indicating whether the items should be deleted from disk as well as from the project file. /// </param> public override bool CanRemove(IImmutableSet <IProjectTree> nodes, DeleteOptions deleteOptions = DeleteOptions.None) { if (deleteOptions.HasFlag(DeleteOptions.DeleteFromStorage)) { return(false); } DependenciesSnapshot snapshot = _dependenciesSnapshotProvider.CurrentSnapshot; if (snapshot == null) { return(false); } foreach (IProjectTree node in nodes) { if (!node.Flags.Contains(DependencyTreeFlags.SupportsRemove)) { return(false); } string filePath = UnconfiguredProject.MakeRelative(node.FilePath); if (string.IsNullOrEmpty(filePath)) { continue; } IDependency?dependency = snapshot.FindDependency(filePath, topLevel: true); if (dependency == null || dependency.Implicit) { return(false); } } return(true); }
/// <summary> /// Deletes items from the project, and optionally from disk. /// Note: Delete and Remove commands are handled via IVsHierarchyDeleteHandler3, not by /// IAsyncCommandGroupHandler and first asks us we CanRemove nodes. If yes then RemoveAsync is called. /// We can remove only nodes that are standard and based on project items, i.e. nodes that /// are created by default IProjectDependenciesSubTreeProvider implementations and have /// DependencyNode.GenericDependencyFlags flags and IRule with Context != null, in order to obtain /// node's itemSpec. ItemSpec then used to remove a project item having same Include. /// </summary> /// <param name="nodes">The nodes that should be deleted.</param> /// <param name="deleteOptions">A value indicating whether the items should be deleted from disk as well as /// from the project file. /// </param> /// <exception cref="InvalidOperationException">Thrown when <see cref="IProjectTreeProvider.CanRemove"/> /// would return <c>false</c> for this operation.</exception> public override async Task RemoveAsync(IImmutableSet <IProjectTree> nodes, DeleteOptions deleteOptions = DeleteOptions.None) { if (deleteOptions.HasFlag(DeleteOptions.DeleteFromStorage)) { throw new NotSupportedException(); } // Get the list of shared import nodes. IEnumerable <IProjectTree> sharedImportNodes = nodes.Where(node => node.Flags.Contains(DependencyTreeFlags.SharedProjectFlags)); // Get the list of normal reference Item Nodes (this excludes any shared import nodes). IEnumerable <IProjectTree> referenceItemNodes = nodes.Except(sharedImportNodes); Assumes.NotNull(ActiveConfiguredProject); await _projectAccessor.OpenProjectForWriteAsync(ActiveConfiguredProject, project => { // Handle the removal of normal reference Item Nodes (this excludes any shared import nodes). foreach (IProjectTree node in referenceItemNodes) { if (node.BrowseObjectProperties?.Context == null) { // if node does not have an IRule with valid ProjectPropertiesContext we can not // get its itemsSpec. If nodes provided by custom IProjectDependenciesSubTreeProvider // implementation, and have some custom IRule without context, it is not a problem, // since they would not have DependencyNode.GenericDependencyFlags and we would not // end up here, since CanRemove would return false and Remove command would not show // up for those nodes. continue; } IProjectPropertiesContext nodeItemContext = node.BrowseObjectProperties.Context; ProjectItem?unresolvedReferenceItem = project.GetItemsByEvaluatedInclude(nodeItemContext.ItemName) .FirstOrDefault( (item, t) => string.Equals(item.ItemType, t, StringComparisons.ItemTypes), nodeItemContext.ItemType); Report.IfNot(unresolvedReferenceItem != null, "Cannot find reference to remove."); if (unresolvedReferenceItem != null) { project.RemoveItem(unresolvedReferenceItem); } } DependenciesSnapshot snapshot = _dependenciesSnapshotProvider.CurrentSnapshot; Requires.NotNull(snapshot, nameof(snapshot)); if (snapshot == null) { return; } // Handle the removal of shared import nodes. ProjectRootElement projectXml = project.Xml; foreach (IProjectTree sharedImportNode in sharedImportNodes) { string sharedFilePath = UnconfiguredProject.MakeRelative(sharedImportNode.FilePath); if (string.IsNullOrEmpty(sharedFilePath)) { continue; } IDependency?sharedProjectDependency = snapshot.FindDependency(sharedFilePath, topLevel: true); if (sharedProjectDependency != null) { sharedFilePath = sharedProjectDependency.Path; } // Find the import that is included in the evaluation of the specified ConfiguredProject that // imports the project file whose full path matches the specified one. IEnumerable <ResolvedImport> matchingImports = from import in project.Imports where import.ImportingElement.ContainingProject == projectXml && PathHelper.IsSamePath(import.ImportedProject.FullPath, sharedFilePath) select import; foreach (ResolvedImport importToRemove in matchingImports) { ProjectImportElement importingElementToRemove = importToRemove.ImportingElement; Report.IfNot(importingElementToRemove != null, "Cannot find shared project reference to remove."); if (importingElementToRemove != null) { importingElementToRemove.Parent.RemoveChild(importingElementToRemove); } } } }); }
public async Task <IProjectTree> BuildTreeAsync( IProjectTree dependenciesTree, DependenciesSnapshot snapshot, CancellationToken cancellationToken = default) { // Keep a reference to the original tree to return in case we are cancelled. IProjectTree originalTree = dependenciesTree; bool hasSingleTarget = snapshot.DependenciesByTargetFramework.Count(x => !x.Key.Equals(TargetFramework.Any)) == 1; var currentTopLevelNodes = new HashSet <IProjectTree>(); if (hasSingleTarget) { await BuildSingleTargetTreeAsync(); } else { await BuildMultiTargetTreeAsync(); } if (cancellationToken.IsCancellationRequested) { return(originalTree); } dependenciesTree = CleanupOldNodes(dependenciesTree, currentTopLevelNodes); ProjectImageMoniker rootIcon = _viewModelFactory.GetDependenciesRootIcon(snapshot.HasReachableVisibleUnresolvedDependency).ToProjectSystemType(); return(dependenciesTree.SetProperties(icon: rootIcon, expandedIcon: rootIcon)); async Task BuildSingleTargetTreeAsync() { foreach ((ITargetFramework _, TargetedDependenciesSnapshot targetedSnapshot) in snapshot.DependenciesByTargetFramework) { if (cancellationToken.IsCancellationRequested) { return; } dependenciesTree = await BuildSubTreesAsync( rootNode : dependenciesTree, snapshot.ActiveTargetFramework, targetedSnapshot, RememberNewNodes); } } async Task BuildMultiTargetTreeAsync() { foreach ((ITargetFramework targetFramework, TargetedDependenciesSnapshot targetedSnapshot) in snapshot.DependenciesByTargetFramework) { if (cancellationToken.IsCancellationRequested) { return; } if (targetFramework.Equals(TargetFramework.Any)) { dependenciesTree = await BuildSubTreesAsync( rootNode : dependenciesTree, snapshot.ActiveTargetFramework, targetedSnapshot, RememberNewNodes); } else { IProjectTree? node = dependenciesTree.FindChildWithCaption(targetFramework.FriendlyName); bool shouldAddTargetNode = node == null; IDependencyViewModel targetViewModel = _viewModelFactory.CreateTargetViewModel(targetedSnapshot); node = CreateOrUpdateNode( node, targetViewModel, browseObjectProperties: null, isProjectItem: false, additionalFlags: ProjectTreeFlags.Create(ProjectTreeFlags.Common.BubbleUp)); node = await BuildSubTreesAsync( rootNode : node, snapshot.ActiveTargetFramework, targetedSnapshot, CleanupOldNodes); dependenciesTree = shouldAddTargetNode ? dependenciesTree.Add(node).Parent ! : node.Parent !; Assumes.NotNull(dependenciesTree); currentTopLevelNodes.Add(node); } } } IProjectTree RememberNewNodes(IProjectTree rootNode, IEnumerable <IProjectTree> currentNodes) { if (currentNodes != null) { currentTopLevelNodes.AddRange(currentNodes); } return(rootNode); } }
protected override void ProcessInputNode(IGraphContext graphContext, GraphNode inputGraphNode, IDependency dependency, DependenciesSnapshot snapshot, IDependenciesGraphViewProvider viewProvider, string projectPath, ref bool trackChanges) { inputGraphNode.SetValue(DependenciesGraphSchema.DependencyIdProperty, dependency.Id); inputGraphNode.SetValue(DependenciesGraphSchema.ResolvedProperty, dependency.Resolved); if (viewProvider.HasChildren(dependency)) { inputGraphNode.SetValue(DgmlNodeProperties.ContainsChildren, true); } }
/// <summary> /// ProjectContextChanged gets fired every time dependencies change for projects across solution. /// <see cref="_expandedGraphContexts"/> contains all nodes that we need to check for potential updates /// in their child dependencies. /// </summary> private void OnSnapshotChanged(object sender, SnapshotChangedEventArgs e) { DependenciesSnapshot snapshot = e.Snapshot; if (snapshot == null || e.Token.IsCancellationRequested) { return; } // _expandedGraphContexts remembers graph expanded or checked so far. // Each context represents one level in the graph, i.e. a node and its first level dependencies // Tracking changes over all expanded contexts ensures that all levels are processed // and updated when there are any changes in nodes data. lock (_lock) { foreach (IGraphContext graphContext in _expandedGraphContexts) { if (e.Token.IsCancellationRequested) { return; } bool anyChanges = false; try { if (HandleChanges(graphContext)) { anyChanges = true; } } finally { // Calling OnCompleted ensures that the changes are reflected in UI if (anyChanges) { graphContext.OnCompleted(); } } } } return; bool HandleChanges(IGraphContext graphContext) { bool anyChanges = false; foreach (GraphNode inputGraphNode in graphContext.InputNodes.ToList()) { string?existingDependencyId = inputGraphNode.GetValue <string>(DependenciesGraphSchema.DependencyIdProperty); if (string.IsNullOrEmpty(existingDependencyId)) { continue; } string?nodeProjectPath = inputGraphNode.Id.GetValue(CodeGraphNodeIdName.Assembly); if (string.IsNullOrEmpty(nodeProjectPath)) { continue; } DependenciesSnapshot?updatedSnapshot = _aggregateSnapshotProvider.GetSnapshot(nodeProjectPath !); IDependency?updatedDependency = updatedSnapshot?.FindDependency(existingDependencyId); if (updatedDependency == null) // or updatedSnapshot == null { continue; } IDependenciesGraphViewProvider?viewProvider = _viewProviders .FirstOrDefaultValue((x, d) => x.SupportsDependency(d), updatedDependency); if (viewProvider == null) { continue; } if (!viewProvider.ShouldApplyChanges(nodeProjectPath !, snapshot.ProjectPath, updatedDependency)) { continue; } using var scope = new GraphTransactionScope(); if (viewProvider.ApplyChanges( graphContext, nodeProjectPath !, updatedDependency, inputGraphNode, updatedSnapshot !.DependenciesByTargetFramework[updatedDependency.TargetFramework])) { anyChanges = true; } scope.Complete(); } return(anyChanges); } }
/// <summary> /// When some other project's snapshot changed we need to check if our snapshot has a top level /// dependency on changed project. If it does we need to refresh those top level dependencies to /// reflect changes. /// </summary> /// <param name="thisProjectSnapshot"></param> /// <param name="otherProjectSnapshot"></param> /// <param name="shouldBeResolved"> /// Specifies if top-level project dependencies resolved status. When other project just had its dependencies /// changed, it is resolved=true (we check target's support when we add project dependencies). However when /// other project is unloaded, we should mark top-level dependencies as unresolved. /// </param> /// <param name="token"></param> private void OnOtherProjectDependenciesChanged( DependenciesSnapshot thisProjectSnapshot, DependenciesSnapshot otherProjectSnapshot, bool shouldBeResolved, CancellationToken token) { if (token.IsCancellationRequested || StringComparers.Paths.Equals(thisProjectSnapshot.ProjectPath, otherProjectSnapshot.ProjectPath)) { // if any of the snapshots is not provided or this is the same project - skip return; } string otherProjectPath = otherProjectSnapshot.ProjectPath; foreach ((ITargetFramework _, TargetedDependenciesSnapshot targetedDependencies) in thisProjectSnapshot.DependenciesByTargetFramework) { foreach (IDependency dependency in targetedDependencies.TopLevelDependencies) { if (token.IsCancellationRequested) { return; } // We're only interested in project dependencies if (!StringComparers.DependencyProviderTypes.Equals(dependency.ProviderType, ProviderTypeString)) { continue; } if (!StringComparers.Paths.Equals(otherProjectPath, dependency.FullPath)) { continue; } RaiseChangeEvent(dependency); break; } } return; void RaiseChangeEvent(IDependency dependency) { IDependencyModel model = CreateDependencyModel( dependency.Path, dependency.OriginalItemSpec, shouldBeResolved, dependency.Implicit, dependency.BrowseObjectProperties); var changes = new DependenciesChangesBuilder(); // avoid unnecessary removing since, add would upgrade dependency in snapshot anyway, // but remove would require removing item from the tree instead of in-place upgrade. if (!shouldBeResolved) { changes.Removed(ProviderTypeString, model.Id); } changes.Added(model); FireDependenciesChanged( new DependenciesChangedEventArgs( this, dependency.TargetFramework.FullName, changes.TryBuildChanges() !, token)); } }
private void UpdateDependenciesSnapshot( ImmutableDictionary <ITargetFramework, IDependenciesChanges> changes, IProjectCatalogSnapshot catalogs, ITargetFramework activeTargetFramework, CancellationToken token) { IImmutableSet <string> projectItemSpecs = GetProjectItemSpecsFromSnapshot(); bool anyChanges = false; // Note: we are updating existing snapshot, not receiving a complete new one. Thus we must // ensure incremental updates are done in the correct order. This lock ensures that here. lock (_snapshotLock) { var updatedSnapshot = DependenciesSnapshot.FromChanges( _commonServices.Project.FullPath, _currentSnapshot, changes, catalogs, activeTargetFramework, _snapshotFilters.ToImmutableValueArray(), _subTreeProviders.ToValueDictionary(p => p.ProviderType), projectItemSpecs); if (!ReferenceEquals(_currentSnapshot, updatedSnapshot)) { _currentSnapshot = updatedSnapshot; anyChanges = true; } } if (anyChanges) { // avoid unnecessary tree updates ScheduleDependenciesUpdate(token); } return; // Gets the set of items defined directly the project, and not included by imports. IImmutableSet <string> GetProjectItemSpecsFromSnapshot() { // We don't have catalog snapshot, we're likely updating because one of our project // dependencies changed. Just return 'no data' if (catalogs == null) { return(null); } ImmutableHashSet <string> .Builder itemSpecs = ImmutableHashSet.CreateBuilder(StringComparer.OrdinalIgnoreCase); foreach (ProjectItemInstance item in catalogs.Project.ProjectInstance.Items) { if (item.IsImported()) { continue; } // Returns unescaped evaluated include string itemSpec = item.EvaluatedInclude; if (itemSpec.Length != 0) { itemSpecs.Add(itemSpec); } } return(itemSpecs.ToImmutable()); } }
protected override void ProcessInputNode(IGraphContext graphContext, GraphNode inputGraphNode, IDependency dependency, DependenciesSnapshot snapshot, IDependenciesGraphViewProvider viewProvider, string projectPath, ref bool trackChanges) { if (graphContext.TrackChanges) { trackChanges = true; } viewProvider.BuildGraph( graphContext, projectPath, dependency, inputGraphNode, snapshot.DependenciesByTargetFramework[dependency.TargetFramework]); }