/// <summary> /// Updates the shared project import nodes that are shown under the 'Dependencies/Projects' node. /// </summary> /// <param name="sharedFolders">Snapshot of shared folders.</param> /// <param name="dependenciesChange"></param> /// <returns></returns> protected override void ProcessSharedProjectImportNodes(IProjectSharedFoldersSnapshot sharedFolders, DependenciesChange dependenciesChange) { Requires.NotNull(sharedFolders, nameof(sharedFolders)); Requires.NotNull(dependenciesChange, nameof(dependenciesChange)); var sharedFolderProjectPaths = sharedFolders.Value.Select(sf => sf.ProjectPath); var rootNodeChildren = RootNode.Children; var currentSharedImportNodes = rootNodeChildren .Where(x => x.Flags.Contains(ProjectTreeFlags.Common.SharedProjectImportReference)); var currentSharedImportNodePaths = currentSharedImportNodes.Select(x => x.Id.ItemSpec); // process added nodes IEnumerable <string> addedSharedImportPaths = sharedFolderProjectPaths.Except(currentSharedImportNodePaths); var itemType = ResolvedProjectReference.PrimaryDataSourceItemType; foreach (string addedSharedImportPath in addedSharedImportPaths) { rootNodeChildren = RootNode.Children; var node = rootNodeChildren.FindNode(addedSharedImportPath, itemType); if (node == null) { var sharedFlags = ProjectTreeFlags.Create(ProjectTreeFlags.Common.SharedProjectImportReference); var id = new DependencyNodeId(ProviderType, addedSharedImportPath, itemType); node = new SharedProjectDependencyNode(id, flags: sharedFlags); dependenciesChange.AddedNodes.Add(node); } } // process removed nodes var removedSharedImportPaths = currentSharedImportNodePaths.Except(sharedFolderProjectPaths); foreach (string removedSharedImportPath in removedSharedImportPaths) { var existingImportNode = currentSharedImportNodes .Where(node => PathHelper.IsSamePath(node.Id.ItemSpec, removedSharedImportPath)) .FirstOrDefault(); if (existingImportNode != null) { dependenciesChange.RemovedNodes.Add(existingImportNode); } } }
protected virtual void ProcessDuplicatedNodes(DependenciesChange dependenciesChange) { // Now add new nodes and dedupe any nodes that might have same caption. // For dedupping we apply aliases to all nodes with similar Caption. Alias // is a "Caption (ItemSpec)" and is unique. We try to find existing node with // with the same caption, if found we apply alias to both nodes. If not found // we also check if there are nodes with alias already applied earlier and having // same caption. If yes, we just need to apply alias to our current node only. foreach (var nodeToAdd in dependenciesChange.AddedNodes) { var rootNodeChildren = RootNode.Children; var shouldApplyAlias = false; var matchingChild = rootNodeChildren.FirstOrDefault( x => x.Caption.Equals(nodeToAdd.Caption, StringComparison.OrdinalIgnoreCase)); if (matchingChild == null) { shouldApplyAlias = rootNodeChildren.Any( x => x.Caption.Equals( string.Format(CultureInfo.CurrentCulture, "{0} ({1})", nodeToAdd.Caption, x.Id.ItemSpec), StringComparison.OrdinalIgnoreCase)); } else { shouldApplyAlias = true; } if (shouldApplyAlias) { if (matchingChild != null) { matchingChild.SetProperties(caption: matchingChild.Alias); dependenciesChange.UpdatedNodes.Add(matchingChild); } nodeToAdd.SetProperties(caption: nodeToAdd.Alias); } RootNode.AddChild(nodeToAdd); } }
/// <summary> /// Updates the shared project import nodes that are shown under the 'Dependencies/Projects' node. /// </summary> /// <param name="sharedFolders">Snapshot of shared folders.</param> /// <param name="dependenciesChange"></param> /// <returns></returns> protected virtual void ProcessSharedProjectImportNodes(IProjectSharedFoldersSnapshot sharedFolders, DependenciesChange dependenciesChange) { // does nothing by default }
protected virtual DependenciesChange ProcessDependenciesChanges( IProjectSubscriptionUpdate projectSubscriptionUpdate, IProjectCatalogSnapshot catalogs) { var changes = projectSubscriptionUpdate.ProjectChanges; var resolvedReferenceChanges = ResolvedReferenceRuleNames.Where(x => changes.Keys.Contains(x)) .Select(ruleName => changes[ruleName]).ToImmutableHashSet(); var unresolvedReferenceSnapshots = changes.Values .Where(cd => !ResolvedReferenceRuleNames.Any(ruleName => string.Equals(ruleName, cd.After.RuleName, StringComparison.OrdinalIgnoreCase))) .ToDictionary(d => d.After.RuleName, d => d, StringComparer.OrdinalIgnoreCase); var rootTreeNodes = new HashSet <IDependencyNode>(RootNode.Children); var dependenciesChange = new DependenciesChange(); foreach (var unresolvedChange in unresolvedReferenceSnapshots.Values) { if (!unresolvedChange.Difference.AnyChanges) { continue; } var itemType = GetItemTypeFromRuleName(unresolvedChange.After.RuleName, catalogs, true); if (itemType == null) { // We must be missing that rule. Skip it. continue; } foreach (string removedItemSpec in unresolvedChange.Difference.RemovedItems) { var node = rootTreeNodes.FindNode(removedItemSpec, itemType); if (node != null) { dependenciesChange.RemovedNodes.Add(node); } } foreach (string addedItemSpec in unresolvedChange.Difference.AddedItems) { var node = rootTreeNodes.FindNode(addedItemSpec, itemType); if (node == null) { var properties = GetProjectItemProperties(unresolvedChange.After, addedItemSpec); node = CreateDependencyNode(addedItemSpec, itemType, properties: properties, resolved: false); dependenciesChange.AddedNodes.Add(node); } } } var updatedUnresolvedSnapshots = unresolvedReferenceSnapshots.Values.Select(cd => cd.After); foreach (var resolvedReferenceRuleChanges in resolvedReferenceChanges) { if (!resolvedReferenceRuleChanges.Difference.AnyChanges) { continue; } // if resolved reference appears in Removed list, it means that it is either removed from // project or can not be resolved anymore. In case when it can not be resolved, // we must remove old "resolved" node and add new unresolved node with corresponding // properties changes (rules, icon, etc) // Note: removed resolved node is not added to "added unresolved diff", which we process // above, thus we need to do this properties update here. It is just cleaner to re-add node // instead of modifying properties. foreach (string removedItemSpec in resolvedReferenceRuleChanges.Difference.RemovedItems) { string unresolvedItemSpec = resolvedReferenceRuleChanges.Before .Items[removedItemSpec][OriginalItemSpecPropertyName]; IProjectRuleSnapshot unresolvedReferenceSnapshot = null; string unresolvedItemType = GetUnresolvedReferenceItemType(unresolvedItemSpec, updatedUnresolvedSnapshots, catalogs, out unresolvedReferenceSnapshot); var node = rootTreeNodes.FindNode(removedItemSpec, unresolvedItemType); if (node != null) { dependenciesChange.RemovedNodes.Add(node); IImmutableDictionary <string, string> properties = null; if (unresolvedReferenceSnapshot != null) { properties = GetProjectItemProperties(unresolvedReferenceSnapshot, unresolvedItemSpec); } node = CreateDependencyNode(unresolvedItemSpec, unresolvedItemType, properties: properties, resolved: false); dependenciesChange.AddedNodes.Add(node); } } foreach (string addedItemSpec in resolvedReferenceRuleChanges.Difference.AddedItems) { var properties = GetProjectItemProperties(resolvedReferenceRuleChanges.After, addedItemSpec); if (properties == null || !properties.Keys.Contains(OriginalItemSpecPropertyName)) { // if there no OriginalItemSpec, we can not associate item with the rule continue; } var originalItemSpec = properties[OriginalItemSpecPropertyName]; IProjectRuleSnapshot unresolvedReferenceSnapshot = null; var itemType = GetUnresolvedReferenceItemType(originalItemSpec, updatedUnresolvedSnapshots, catalogs, out unresolvedReferenceSnapshot); if (string.IsNullOrEmpty(itemType)) { // Note: design time build resolves not only our unresolved assemblies, but also // all transitive assembly dependencies, which ar enot direct references and // we should not show them. If reference does not have an unresolved reference // corresponded to it, i.e. itemType = null here - we skip it. continue; } // avoid adding unresolved dependency along with resolved one var existingUnresolvedNode = dependenciesChange.AddedNodes.FindNode(originalItemSpec, itemType); if (existingUnresolvedNode != null) { dependenciesChange.AddedNodes.Remove(existingUnresolvedNode); } // if unresolved dependency was added earlier, remove it, since it will be substituted by resolved one existingUnresolvedNode = rootTreeNodes.FindNode(originalItemSpec, itemType); if (existingUnresolvedNode != null) { dependenciesChange.RemovedNodes.Add(existingUnresolvedNode); } var newNode = CreateDependencyNode(originalItemSpec, itemType: itemType, properties: properties); dependenciesChange.AddedNodes.Add(newNode); } } return(dependenciesChange); }
public void TestProcessDuplicatedNodes(DependenciesChange dependenciesChange) { ProcessDuplicatedNodes(dependenciesChange); }
public void TestProcessDuplicatedNodes(DependenciesChange changes) { ProcessDuplicatedNodes(changes); }
protected override void ProcessDuplicatedNodes(DependenciesChange dependenciesChange) { // do nothing - we don't need to do anything special for duplicated nodes, // since we make sure that there no duplicated packages earlier. }
protected override DependenciesChange ProcessDependenciesChanges( IProjectSubscriptionUpdate projectSubscriptionUpdate, IProjectCatalogSnapshot catalogs) { var changes = projectSubscriptionUpdate.ProjectChanges; var dependenciesChange = new DependenciesChange(); lock (_snapshotLock) { var newDependencies = new HashSet <DependencyMetadata>(); foreach (var change in changes.Values) { if (!change.Difference.AnyChanges) { continue; } foreach (string removedItemSpec in change.Difference.RemovedItems) { CurrentSnapshot.RemoveDependency(removedItemSpec); var itemNode = RootNode.Children.FirstOrDefault( x => x.Id.ItemSpec.Equals(removedItemSpec, StringComparison.OrdinalIgnoreCase)); if (itemNode != null) { dependenciesChange.RemovedNodes.Add(itemNode); } } foreach (string changedItemSpec in change.Difference.ChangedItems) { var properties = GetProjectItemProperties(change.After, changedItemSpec); if (properties == null) { continue; } CurrentSnapshot.UpdateDependency(changedItemSpec, properties); var itemNode = RootNode.Children.FirstOrDefault( x => x.Id.ItemSpec.Equals(changedItemSpec, StringComparison.OrdinalIgnoreCase)); if (itemNode != null) { dependenciesChange.UpdatedNodes.Add(itemNode); } } foreach (string addedItemSpec in change.Difference.AddedItems) { var properties = GetProjectItemProperties(change.After, addedItemSpec); if (properties == null) { continue; } var newDependency = CurrentSnapshot.AddDependency(addedItemSpec, properties); newDependencies.Add(newDependency); } } // since we have limited implementation for multi targeted projects, // we assume that there is only one target - take first target and add // top level nodes for it var currentTarget = CurrentSnapshot.Targets.Keys.FirstOrDefault(); if (currentTarget == null) { return(dependenciesChange); } var currentTargetDependency = CurrentSnapshot.DependenciesWorld[currentTarget]; var currentTargetTopLevelDependencies = currentTargetDependency.DependenciesItemSpecs; var addedTopLevelDependencies = newDependencies.Where( x => currentTargetTopLevelDependencies.Contains(x.ItemSpec)); foreach (var addedDependency in addedTopLevelDependencies) { var itemNode = RootNode.Children.FirstOrDefault( x => x.Id.ItemSpec.Equals(addedDependency.ItemSpec, StringComparison.OrdinalIgnoreCase)); if (itemNode == null) { itemNode = CreateDependencyNode(addedDependency, topLevel: true); dependenciesChange.AddedNodes.Add(itemNode); } } } return(dependenciesChange); }
protected override DependenciesChange ProcessDependenciesChanges( IProjectSubscriptionUpdate projectSubscriptionUpdate, IProjectCatalogSnapshot catalogs) { var dependenciesChange = new DependenciesChange(); // take a snapshot for current top level tree nodes var rootNodes = new HashSet <IDependencyNode>(RootNode.Children); lock (_snapshotLock) { var unresolvedChanges = ProcessUnresolvedChanges(projectSubscriptionUpdate); var resolvedChanges = ProcessResolvedChanges(projectSubscriptionUpdate, rootNodes); // Logic below merges unresolved and resolved pending changes. Resolved should win if there // is similar unresolved dependency. Thus // - for pending removals, we don't care about the order and just remove whatever valid // pending changes are there (valid=existing in the RootNode top level children) // - for pending updates, since ItemSpecs must exist (otherwise it would not be an Update pending // change, but an Add or Remove), we first try to merge resolved pending changes, where for // each change we try to remove similar unresolved pending Update request. // - for pending additions we need to match itemSpecs of the resolved dependencies to names of the // unresolved, since resolved ItemSpec is "tfm/packagename/version", but unresolved is just // "packagename". Thus to avoid same tree node name collision we need to be smart when detecting // similar resolved vs unresolved pending changes. // The algorithm is, first merge resolved changes and if there is a // - matching unresolved item in the RootNode already, submit a removal // - matching unresolved item in pending unresolved additions - remove it form there too // Then, process remaining unresolved changes and before mergin each of them, check if matching // name already exists in RootNode or already merged additions. // Note: if it would became too complicated with time, create a separate PackageDependencyChangesResolver // class that would hide it and make logic here simpler. // remove foreach (var metadata in unresolvedChanges.RemovedNodes) { var itemNode = rootNodes.FirstOrDefault( x => x.Id.ItemSpec.Equals(metadata.ItemSpec, StringComparison.OrdinalIgnoreCase)); if (itemNode != null) { dependenciesChange.RemovedNodes.Add(itemNode); } } foreach (var metadata in resolvedChanges.RemovedNodes) { var itemNode = rootNodes.FirstOrDefault( x => x.Id.ItemSpec.Equals(metadata.ItemSpec, StringComparison.OrdinalIgnoreCase)); if (itemNode != null) { dependenciesChange.RemovedNodes.Add(itemNode); } } // update foreach (var resolvedMetadata in resolvedChanges.UpdatedNodes) { // since it is an update root node must have those item specs, so we can check them var itemNode = rootNodes.FirstOrDefault( x => x.Id.ItemSpec.Equals(resolvedMetadata.ItemSpec, StringComparison.OrdinalIgnoreCase)); if (itemNode != null) { itemNode = CreateDependencyNode(resolvedMetadata, topLevel: true); dependenciesChange.UpdatedNodes.Add(itemNode); } var unresolvedMatch = unresolvedChanges.UpdatedNodes.FirstOrDefault(x => x.ItemSpec.Equals(resolvedMetadata.Name)); if (unresolvedMatch != null) { unresolvedChanges.UpdatedNodes.Remove(unresolvedMatch); } } foreach (var unresolvedMetadata in unresolvedChanges.UpdatedNodes) { // since it is an update root node must have those item specs, so we can check them var itemNode = rootNodes.FirstOrDefault( x => x.Id.ItemSpec.Equals(unresolvedMetadata.ItemSpec, StringComparison.OrdinalIgnoreCase)); if (itemNode != null) { dependenciesChange.UpdatedNodes.Add(itemNode); } } // add foreach (var resolvedMetadata in resolvedChanges.AddedNodes) { // see if there is already node created for unresolved package - if yes, delete it // Note: unresolved packages ItemSpec contain only package name, when resolved package ItemSpec // contains TFM/PackageName/version, so we need to check for name if we want to find unresolved // packages. var itemNode = rootNodes.FirstOrDefault( x => x.Id.ItemSpec.Equals(resolvedMetadata.Name, StringComparison.OrdinalIgnoreCase)); if (itemNode != null) { dependenciesChange.RemovedNodes.Add(itemNode); } // see if there no node with the same resolved metadata - if no, create it itemNode = rootNodes.FirstOrDefault( x => x.Id.ItemSpec.Equals(resolvedMetadata.ItemSpec, StringComparison.OrdinalIgnoreCase)); if (itemNode == null) { itemNode = CreateDependencyNode(resolvedMetadata, topLevel: true); dependenciesChange.AddedNodes.Add(itemNode); } // avoid adding matching unresolved packages var unresolvedMatch = unresolvedChanges.AddedNodes.FirstOrDefault(x => x.ItemSpec.Equals(resolvedMetadata.Name)); if (unresolvedMatch != null) { unresolvedChanges.AddedNodes.Remove(unresolvedMatch); } } foreach (var unresolvedMetadata in unresolvedChanges.AddedNodes) { var itemNode = rootNodes.FirstOrDefault(x => DoesNodeMatchByNameOrItemSpec(x, unresolvedMetadata.ItemSpec)); if (itemNode == null) { // in case when unresolved come together with resolved data, root nodes might not yet have // an unresolved node and we need to check if we did add resolved one above to avoid collision. itemNode = dependenciesChange.AddedNodes.FirstOrDefault( x => DoesNodeMatchByNameOrItemSpec(x, unresolvedMetadata.ItemSpec)); } if (itemNode == null) { itemNode = CreateDependencyNode(unresolvedMetadata, topLevel: true); dependenciesChange.AddedNodes.Add(itemNode); } } } return(dependenciesChange); }