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);
                }
            }
        }
Пример #2
0
            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);
            }
Пример #3
0
        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);
        }
Пример #4
0
        /// <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();
        }
Пример #8
0
            /// <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);
            }
Пример #9
0
        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();
        }
Пример #11
0
        /// <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);
        }
Пример #12
0
        /// <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);
                        }
                    }
                }
            });
        }
Пример #13
0
        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);
            }
        }
Пример #14
0
        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);
            }
        }
Пример #16
0
        /// <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]);
        }