private void AddEdgesFromProjectReferenceItems(Dictionary <ConfigurationMetadata, ParsedProject> allParsedProjects, GraphEdges edges) { var transitiveReferenceCache = new Dictionary <ProjectGraphNode, List <ProjectGraphNode> >(allParsedProjects.Count); foreach (var parsedProject in allParsedProjects) { var currentNode = parsedProject.Value.GraphNode; var requiresTransitiveProjectReferences = _projectInterpretation.RequiresTransitiveProjectReferences(currentNode.ProjectInstance); foreach (var referenceInfo in parsedProject.Value.ReferenceInfos) { // Always add direct references. currentNode.AddProjectReference( allParsedProjects[referenceInfo.ReferenceConfiguration].GraphNode, referenceInfo.ProjectReferenceItem, edges); // Add transitive references only if the project requires it. if (requiresTransitiveProjectReferences) { foreach (var transitiveProjectReference in GetTransitiveProjectReferences(allParsedProjects[referenceInfo.ReferenceConfiguration])) { currentNode.AddProjectReference( transitiveProjectReference, new ProjectItemInstance( project: currentNode.ProjectInstance, itemType: ProjectInterpretation.TransitiveReferenceItemName, includeEscaped: referenceInfo.ReferenceConfiguration.ProjectFullPath, directMetadata: null, definingFileEscaped: currentNode.ProjectInstance.FullPath ), edges); } } } } List <ProjectGraphNode> GetTransitiveProjectReferences(ParsedProject parsedProject) { if (transitiveReferenceCache.TryGetValue(parsedProject.GraphNode, out List <ProjectGraphNode> cachedTransitiveReferences)) { return(cachedTransitiveReferences); } else { var transitiveReferences = new List <ProjectGraphNode>(); foreach (var referenceInfo in parsedProject.ReferenceInfos) { transitiveReferences.Add(allParsedProjects[referenceInfo.ReferenceConfiguration].GraphNode); transitiveReferences.AddRange(GetTransitiveProjectReferences(allParsedProjects[referenceInfo.ReferenceConfiguration])); } transitiveReferenceCache.Add(parsedProject.GraphNode, transitiveReferences); return(transitiveReferences); } } }
private void AddEdgesFromProjectReferenceItems(Dictionary <ConfigurationMetadata, ParsedProject> allParsedProjects, GraphEdges edges) { Dictionary <ProjectGraphNode, HashSet <ProjectGraphNode> > transitiveReferenceCache = new(allParsedProjects.Count); foreach (var parsedProject in allParsedProjects) { var currentNode = parsedProject.Value.GraphNode; var requiresTransitiveProjectReferences = _projectInterpretation.RequiresTransitiveProjectReferences(currentNode.ProjectInstance); foreach (var referenceInfo in parsedProject.Value.ReferenceInfos) { // Always add direct references. currentNode.AddProjectReference( allParsedProjects[referenceInfo.ReferenceConfiguration].GraphNode, referenceInfo.ProjectReferenceItem, edges); // Add transitive references only if the project requires it. if (requiresTransitiveProjectReferences) { foreach (var transitiveProjectReference in GetTransitiveProjectReferencesExcludingSelf(allParsedProjects[referenceInfo.ReferenceConfiguration])) { currentNode.AddProjectReference( transitiveProjectReference, new ProjectItemInstance( project: currentNode.ProjectInstance, itemType: ProjectInterpretation.TransitiveReferenceItemName, includeEscaped: referenceInfo.ReferenceConfiguration.ProjectFullPath, directMetadata: null, definingFileEscaped: currentNode.ProjectInstance.FullPath ), edges); } } } } HashSet <ProjectGraphNode> GetTransitiveProjectReferencesExcludingSelf(ParsedProject parsedProject) { if (transitiveReferenceCache.TryGetValue(parsedProject.GraphNode, out HashSet <ProjectGraphNode> transitiveReferences)) { return(transitiveReferences); } transitiveReferences = new(); // Add the results to the cache early, even though it'll be incomplete until the loop below finishes. This helps handle cycles by not allowing them to recurse infinitely. // Note that this makes transitive references incomplete in the case of a cycle, but direct dependencies are always added so a cycle will still be detected and an exception will still be thrown. transitiveReferenceCache[parsedProject.GraphNode] = transitiveReferences; foreach (ProjectInterpretation.ReferenceInfo referenceInfo in parsedProject.ReferenceInfos) { ParsedProject reference = allParsedProjects[referenceInfo.ReferenceConfiguration]; transitiveReferences.Add(reference.GraphNode); // Perf note: avoiding UnionWith to avoid boxing the HashSet enumerator. foreach (ProjectGraphNode transitiveReference in GetTransitiveProjectReferencesExcludingSelf(reference)) { transitiveReferences.Add(transitiveReference); } } return(transitiveReferences); } }
private void AddEdgesFromProjectReferenceItems(Dictionary <ConfigurationMetadata, ParsedProject> allParsedProjects, GraphEdges edges) { var transitiveReferenceCache = new Dictionary <ProjectGraphNode, HashSet <ProjectGraphNode> >(allParsedProjects.Count); foreach (var parsedProject in allParsedProjects) { var currentNode = parsedProject.Value.GraphNode; var requiresTransitiveProjectReferences = _projectInterpretation.RequiresTransitiveProjectReferences(currentNode.ProjectInstance); foreach (var referenceInfo in parsedProject.Value.ReferenceInfos) { // Always add direct references. currentNode.AddProjectReference( allParsedProjects[referenceInfo.ReferenceConfiguration].GraphNode, referenceInfo.ProjectReferenceItem, edges); // Add transitive references only if the project requires it. if (requiresTransitiveProjectReferences) { foreach (var transitiveProjectReference in GetTransitiveProjectReferencesExcludingSelf(allParsedProjects[referenceInfo.ReferenceConfiguration])) { currentNode.AddProjectReference( transitiveProjectReference, new ProjectItemInstance( project: currentNode.ProjectInstance, itemType: ProjectInterpretation.TransitiveReferenceItemName, includeEscaped: referenceInfo.ReferenceConfiguration.ProjectFullPath, directMetadata: null, definingFileEscaped: currentNode.ProjectInstance.FullPath ), edges); } } } } HashSet <ProjectGraphNode> GetTransitiveProjectReferencesExcludingSelf(ParsedProject parsedProject) { HashSet <ProjectGraphNode> references = new(); GetTransitiveProjectReferencesExcludingSelfHelper(parsedProject, references, null); return(references); } // transitiveReferences contains all of the references we've found so far from the initial GetTransitiveProjectReferencesExcludingSelf call. // referencesFromHere is essentially "reset" at each level of the recursion. // The first is important because if we find a cycle at some point, we need to know not to keep recursing. We wouldn't have added to transitiveReferenceCache yet, since we haven't finished // finding all the transitive references yet. // On the other hand, the second is important to help us fill that cache afterwards. The cache is from a particular node to all of its references, including transitive references // but not including itself, which means we can't include parents as we would if we used transitiveReferences. You can see that for any particular call, it creates a new "toCache" // HashSet that we fill with direct references and pass as referencesFromHere in recursive calls to fill it with transitive references. It is then used to populate the cache. // Meanwhile, we avoid going into the recursive step at all if transitiveReferences already includes a particular node to avoid a StackOverflowException if there's a loop. void GetTransitiveProjectReferencesExcludingSelfHelper(ParsedProject parsedProject, HashSet <ProjectGraphNode> traversedReferences, HashSet <ProjectGraphNode> incompleteReferencesOfDirectlyReferencingNode) { if (transitiveReferenceCache.TryGetValue(parsedProject.GraphNode, out HashSet <ProjectGraphNode> cachedTransitiveReferences)) { traversedReferences.UnionWith(cachedTransitiveReferences); } else { HashSet <ProjectGraphNode> referencesFromThisNode = new(); foreach (ProjectInterpretation.ReferenceInfo referenceInfo in parsedProject.ReferenceInfos) { ParsedProject reference = allParsedProjects[referenceInfo.ReferenceConfiguration]; if (traversedReferences.Add(reference.GraphNode)) { GetTransitiveProjectReferencesExcludingSelfHelper(reference, traversedReferences, referencesFromThisNode); } else if (transitiveReferenceCache.TryGetValue(reference.GraphNode, out cachedTransitiveReferences)) { referencesFromThisNode.UnionWith(cachedTransitiveReferences); } referencesFromThisNode.Add(reference.GraphNode); } // We've returned from recursing through all transitive references // of this node, so add that set to the cache transitiveReferenceCache[parsedProject.GraphNode] = referencesFromThisNode; if (incompleteReferencesOfDirectlyReferencingNode is not null) { // Also add it to the set of transitive dependencies of // the referencing node (which are probably still incomplete) incompleteReferencesOfDirectlyReferencingNode.UnionWith(referencesFromThisNode); } } } }