private List <ProjectInterpretation.ReferenceInfo> ParseReferences(ProjectGraphNode parsedProject) { var referenceInfos = new List <ProjectInterpretation.ReferenceInfo>(); foreach (var referenceInfo in _projectInterpretation.GetReferences(parsedProject.ProjectInstance)) { if (FileUtilities.IsSolutionFilename(referenceInfo.ReferenceConfiguration.ProjectFullPath)) { throw new InvalidOperationException(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword( "StaticGraphDoesNotSupportSlnReferences", referenceInfo.ReferenceConfiguration.ProjectFullPath, referenceInfo.ReferenceConfiguration.ProjectFullPath )); } SubmitProjectForParsing(referenceInfo.ReferenceConfiguration); referenceInfos.Add(referenceInfo); } return(referenceInfos); }
/// <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); }
/// <remarks> /// Traverse the found nodes and add edges. /// Maintain the state of each node (InProcess and Processed) to detect cycles. /// Returns false if cycles were detected. /// </remarks> private void CreateEdgesAndDetectCycles( List <ConfigurationMetadata> entryPointConfigurationMetadata, ProjectInterpretation sdkInfo, ConcurrentDictionary <ConfigurationMetadata, ProjectGraphNode> allParsedProjects) { var nodeStates = new Dictionary <ProjectGraphNode, NodeVisitationState>(); foreach (var entrypointConfig in entryPointConfigurationMetadata) { var entryPointNode = allParsedProjects[entrypointConfig]; if (!nodeStates.ContainsKey(entryPointNode)) { CreateEdgesAndDetectCyclesForRoot(entryPointNode, entrypointConfig, nodeStates); } else { ErrorUtilities.VerifyThrow( nodeStates[entryPointNode] == NodeVisitationState.Processed, "entrypoints should get processed after a call to detect cycles"); } } return; (bool success, List <string> projectsInCycle) CreateEdgesAndDetectCyclesForRoot( ProjectGraphNode node, ConfigurationMetadata nodeConfig, IDictionary <ProjectGraphNode, NodeVisitationState> nodeState) { nodeState[node] = NodeVisitationState.InProcess; foreach (var referenceConfig in sdkInfo.GetReferences(node.ProjectInstance)) { var referenceNode = allParsedProjects[referenceConfig]; 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> { referenceConfig.ProjectFullPath }; return(false, projectsInCycle); } } else { // recursively process newly discovered references var loadReference = CreateEdgesAndDetectCyclesForRoot(referenceNode, referenceConfig, 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(referenceConfig.ProjectFullPath); 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(referenceConfig.ProjectFullPath); return(false, loadReference.projectsInCycle); } } var parsedProjectReference = allParsedProjects[referenceConfig]; node.AddProjectReference(parsedProjectReference); parsedProjectReference.AddReferencingProject(node); } nodeState[node] = NodeVisitationState.Processed; return(true, null); } }