public GraphBuilder( IEnumerable <ProjectGraphEntryPoint> entryPoints, ProjectCollection projectCollection, ProjectGraph.ProjectInstanceFactoryFunc projectInstanceFactory, ProjectInterpretation projectInterpretation, int degreeOfParallelism, CancellationToken cancellationToken) { var(actualEntryPoints, solutionDependencies) = ExpandSolutionIfPresent(entryPoints.ToImmutableArray()); _solutionDependencies = solutionDependencies; _entryPointConfigurationMetadata = AddGraphBuildPropertyToEntryPoints(actualEntryPoints); IEqualityComparer <ConfigurationMetadata> configComparer = EqualityComparer <ConfigurationMetadata> .Default; _graphWorkSet = new ParallelWorkSet <ConfigurationMetadata, ParsedProject>( degreeOfParallelism - ImplicitWorkerCount, configComparer, cancellationToken); _projectCollection = projectCollection; _projectInstanceFactory = projectInstanceFactory; _projectInterpretation = projectInterpretation; }
/// <remarks> /// Maintain the state of each node (InProcess and Processed) to detect cycles. /// Assumes edges have been added between nodes. /// Returns false if cycles were detected. /// </remarks> private void DetectCycles( IReadOnlyCollection <ProjectGraphNode> entryPointNodes, ProjectInterpretation projectInterpretation, Dictionary <ConfigurationMetadata, ParsedProject> allParsedProjects) { var nodeStates = new Dictionary <ProjectGraphNode, NodeVisitationState>(); foreach (var entryPointNode in entryPointNodes) { if (!nodeStates.ContainsKey(entryPointNode)) { VisitNode(entryPointNode, nodeStates); } else { ErrorUtilities.VerifyThrow( nodeStates[entryPointNode] == NodeVisitationState.Processed, "entrypoints should get processed after a call to detect cycles"); } } return; (bool success, List <string> projectsInCycle) VisitNode( ProjectGraphNode node, IDictionary <ProjectGraphNode, NodeVisitationState> nodeState) { nodeState[node] = NodeVisitationState.InProcess; foreach (var referenceNode in node.ProjectReferences) { if (nodeState.TryGetValue(referenceNode, out var projectReferenceNodeState)) { // Because this is a depth-first search, we should only encounter new nodes or nodes whose subgraph has been completely processed. // If we encounter a node that is currently being processed(InProcess state), it must be one of the ancestors in a circular dependency. if (projectReferenceNodeState == NodeVisitationState.InProcess) { if (node.Equals(referenceNode)) { // the project being evaluated has a reference to itself var selfReferencingProjectString = FormatCircularDependencyError(new List <string> { node.ProjectInstance.FullPath, node.ProjectInstance.FullPath }); throw new CircularDependencyException( string.Format( ResourceUtilities.GetResourceString("CircularDependencyInProjectGraph"), selfReferencingProjectString)); } // the project being evaluated has a circular dependency involving multiple projects // add this project to the list of projects involved in cycle var projectsInCycle = new List <string> { referenceNode.ProjectInstance.FullPath }; return(false, projectsInCycle); } } else { // recursively process newly discovered references var loadReference = VisitNode(referenceNode, nodeState); if (!loadReference.success) { if (loadReference.projectsInCycle[0].Equals(node.ProjectInstance.FullPath)) { // we have reached the nth project in the cycle, form error message and throw loadReference.projectsInCycle.Add(referenceNode.ProjectInstance.FullPath); loadReference.projectsInCycle.Add(node.ProjectInstance.FullPath); var errorMessage = FormatCircularDependencyError(loadReference.projectsInCycle); throw new CircularDependencyException( string.Format( ResourceUtilities.GetResourceString("CircularDependencyInProjectGraph"), errorMessage)); } // this is one of the projects in the circular dependency // update the list of projects in cycle and return the list to the caller loadReference.projectsInCycle.Add(referenceNode.ProjectInstance.FullPath); return(false, loadReference.projectsInCycle); } } } nodeState[node] = NodeVisitationState.Processed; return(true, null); } }