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; }
(IReadOnlyCollection <ProjectGraphNode> EntryPointNodes, IReadOnlyCollection <ProjectGraphNode> RootNodes, IReadOnlyCollection <ProjectGraphNode> AllNodes) LoadGraph( List <ConfigurationMetadata> entryPointConfigurationMetadata, ProjectCollection projectCollection, ProjectInstanceFactoryFunc projectInstanceFactory, ProjectInterpretation sdkInfo) { var allParsedProjects = new ConcurrentDictionary <ConfigurationMetadata, ProjectGraphNode>(); var tasksInProgress = new ConcurrentDictionary <ConfigurationMetadata, object>(); var projectsToEvaluate = new ConcurrentQueue <ConfigurationMetadata>(); foreach (var entryPoint in entryPointConfigurationMetadata) { projectsToEvaluate.Enqueue(entryPoint); } if (FindGraphNodes( projectsToEvaluate, projectCollection, tasksInProgress, projectInstanceFactory, sdkInfo, allParsedProjects, out var exceptions)) { var entryPointNodes = entryPointConfigurationMetadata.Select(e => allParsedProjects[e]).ToList(); CreateEdgesAndDetectCycles(entryPointConfigurationMetadata, sdkInfo, allParsedProjects); var graphRoots = GetGraphRoots(entryPointNodes); ProjectInterpretation.PostProcess(allParsedProjects); return(entryPointNodes.AsReadOnly(), graphRoots.AsReadOnly(), allParsedProjects.Values.ToList()); } else { throw new AggregateException(exceptions); } List <ProjectGraphNode> GetGraphRoots(List <ProjectGraphNode> entryPointNodes) { var graphRoots = new List <ProjectGraphNode>(entryPointNodes.Count); foreach (var entryPointNode in entryPointNodes) { if (entryPointNode.ReferencingProjects.Count == 0) { graphRoots.Add(entryPointNode); } } return(graphRoots); } }
public void BuildGraph( IEnumerable <ProjectGraphEntryPoint> entryPoints, ProjectCollection projectCollection, ProjectGraph.ProjectInstanceFactoryFunc projectInstanceFactory ) { var entryPointConfigurationMetadata = AddGraphBuildPropertyToEntryPoints(entryPoints); var allParsedProjects = new ConcurrentDictionary <ConfigurationMetadata, ProjectGraphNode>(); var projectsToEvaluate = new ConcurrentQueue <ConfigurationMetadata>(entryPointConfigurationMetadata); if (FindGraphNodes( projectsToEvaluate, projectCollection, projectInstanceFactory, ProjectInterpretation, allParsedProjects, out var exceptions)) { Edges = new GraphEdges(); CreateEdgesAndDetectCycles(entryPointConfigurationMetadata, ProjectInterpretation, allParsedProjects, Edges); ProjectInterpretation.PostProcess(allParsedProjects, this); EntryPointNodes = entryPointConfigurationMetadata.Select(e => allParsedProjects[e]).ToList(); RootNodes = GetGraphRoots(EntryPointNodes); ProjectNodes = allParsedProjects.Values.ToList(); } else { throw new AggregateException(exceptions); } IReadOnlyCollection <ProjectGraphNode> GetGraphRoots(IReadOnlyCollection <ProjectGraphNode> entryPointNodes) { var graphRoots = new List <ProjectGraphNode>(entryPointNodes.Count); foreach (var entryPointNode in entryPointNodes) { if (entryPointNode.ReferencingProjects.Count == 0) { graphRoots.Add(entryPointNode); } } graphRoots.TrimExcess(); return(graphRoots); } }
public GraphBuilder( IEnumerable <ProjectGraphEntryPoint> entryPoints, ProjectCollection projectCollection, ProjectGraph.ProjectInstanceFactoryFunc projectInstanceFactory, ProjectInterpretation projectInterpretation, int degreeOfParallelism, CancellationToken cancellationToken) { _entryPointConfigurationMetadata = AddGraphBuildPropertyToEntryPoints(entryPoints); IEqualityComparer <ConfigurationMetadata> configComparer = EqualityComparer <ConfigurationMetadata> .Default; _graphWorkSet = new ParallelWorkSet <ConfigurationMetadata, ProjectGraphNode>( 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); } }
/// <summary> /// Load a graph with root node at entryProjectFile /// Maintain a queue of projects to be processed and evaluate projects in parallel /// Returns false if loading the graph is not successful /// </summary> private bool FindGraphNodes( ConcurrentQueue <ConfigurationMetadata> projectsToEvaluate, ProjectCollection projectCollection, ConcurrentDictionary <ConfigurationMetadata, object> tasksInProgress, ProjectInstanceFactoryFunc projectInstanceFactory, ProjectInterpretation projectInterpretation, ConcurrentDictionary <ConfigurationMetadata, ProjectGraphNode> allParsedProjects, out List <Exception> exceptions) { var exceptionsInTasks = new ConcurrentBag <Exception>(); var evaluationWaitHandle = new AutoResetEvent(false); while (projectsToEvaluate.Count != 0 || tasksInProgress.Count != 0) { ConfigurationMetadata projectToEvaluate; if (projectsToEvaluate.Count != 0) { projectToEvaluate = projectsToEvaluate.Dequeue(); var task = new Task(() => { var parsedProject = CreateNewNode(projectToEvaluate, projectCollection, projectInstanceFactory, allParsedProjects); foreach (var referenceConfig in projectInterpretation.GetReferences(parsedProject.ProjectInstance)) { /*todo: fix the following double check-then-act concurrency bug: one thread can pass the two checks, loose context, * meanwhile another thread passes the same checks with the same data and inserts its reference. The initial thread regains context * and duplicates the information, leading to wasted work */ if (!tasksInProgress.ContainsKey(referenceConfig)) { if (!allParsedProjects.ContainsKey(referenceConfig)) { projectsToEvaluate.Enqueue(referenceConfig); evaluationWaitHandle.Set(); } } } }); if (tasksInProgress.TryAdd(projectToEvaluate, null)) { // once the task completes, remove it from tasksInProgress using a chained task // signal the wait handle to process new projects that have been discovered by this task or exit if all projects have been evaluated task.ContinueWith(_ => { if (task.IsFaulted) { exceptionsInTasks.Add(task.Exception.InnerException); } tasksInProgress.TryRemove(projectToEvaluate, out var _); evaluationWaitHandle.Set(); }); task.Start(); } } else { // if projectsToEvaluate is empty but there are tasks in progress, there is nothing to do till a task completes and discovers new projects // wait till a task completes and sends a signal evaluationWaitHandle.WaitOne(); } } if (exceptionsInTasks.Count != 0) { exceptions = exceptionsInTasks.ToList(); return(false); } exceptions = null; return(true); }
public GraphBuilder(ProjectInterpretation projectInterpretation) { ProjectInterpretation = projectInterpretation; }