internal void AddProjectReference(ProjectGraphNode reference, ProjectItemInstance projectReferenceItem, GraphBuilder.GraphEdges edges) { _projectReferences.Add(reference); reference._referencingProjects.Add(this); // First edge wins, in accordance with vanilla msbuild behaviour when multiple msbuild tasks call into the same logical project edges[(this, reference)] = projectReferenceItem;
private ProjectGraphNode CreateNewNode( ConfigurationMetadata configurationMetadata, ProjectCollection projectCollection, ProjectInstanceFactoryFunc projectInstanceFactory) { // TODO: ProjectInstance just converts the dictionary back to a PropertyDictionary, so find a way to directly provide it. var globalProperties = configurationMetadata.GlobalProperties.ToDictionary(); var projectInstance = projectInstanceFactory( configurationMetadata.ProjectFullPath, globalProperties, projectCollection); if (projectInstance == null) { throw new InvalidOperationException(ResourceUtilities.GetResourceString("NullReferenceFromProjectInstanceFactory")); } var graphNode = new ProjectGraphNode( projectInstance, globalProperties); _allParsedProjects[configurationMetadata] = graphNode; return(graphNode); }
private void ParseReferences(ProjectGraphNode parsedProject) { foreach ((ConfigurationMetadata referenceConfig, _) in _projectInterpretation.GetReferences(parsedProject.ProjectInstance)) { ParseProject(referenceConfig); } }
private static ImmutableList <string> DetermineTargetsToPropagate(ProjectGraphNode node, ImmutableList <string> entryTargets) { var targetsToPropagate = ImmutableList <string> .Empty; ICollection <ProjectItemInstance> projectReferenceTargets = node.ProjectInstance.GetItems(ProjectReferenceTargetsItemType); foreach (var entryTarget in entryTargets) { foreach (var projectReferenceTarget in projectReferenceTargets) { if (projectReferenceTarget.EvaluatedInclude.Equals(entryTarget, StringComparison.OrdinalIgnoreCase)) { string targetsMetadataValue = projectReferenceTarget.GetMetadataValue(ProjectReferenceTargetsMetadataName); targetsToPropagate = targetsToPropagate.AddRange(ExpressionShredder.SplitSemiColonSeparatedList(targetsMetadataValue)); } } } return(targetsToPropagate); }
private ParsedProject ParseProject(ConfigurationMetadata configurationMetadata) { // TODO: ProjectInstance just converts the dictionary back to a PropertyDictionary, so find a way to directly provide it. var globalProperties = configurationMetadata.GlobalProperties.ToDictionary(); var projectInstance = _projectInstanceFactory( configurationMetadata.ProjectFullPath, globalProperties, _projectCollection); if (projectInstance == null) { throw new InvalidOperationException(ResourceUtilities.GetResourceString("NullReferenceFromProjectInstanceFactory")); } var graphNode = new ProjectGraphNode(projectInstance); var referenceInfos = ParseReferences(graphNode); return(new ParsedProject(configurationMetadata, graphNode, referenceInfos)); }
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); }
public ProjectItemInstance this[(ProjectGraphNode node, ProjectGraphNode reference)key]
public ProjectGraphBuildRequest(ProjectGraphNode node, ImmutableList <string> targets) { Node = node ?? throw new ArgumentNullException(nameof(node)); RequestedTargets = targets ?? throw new ArgumentNullException(nameof(targets)); }
/// <remarks> /// Traverse an evaluated graph /// Maintain the state of each node (InProcess and Processed) to detect cycles /// returns false if loading the graph is not successful /// </remarks> private (bool success, List <string> projectsInCycle) DetectCycles(ProjectGraphNode node, Dictionary <ProjectGraphNode, NodeState> nodeState, ProjectCollection projectCollection, PropertyDictionary <ProjectPropertyInstance> globalProperties) { nodeState[node] = NodeState.InProcess; IEnumerable <ProjectItemInstance> projectReferenceItems = node.ProjectInstance.GetItems(MSBuildConstants.ProjectReferenceItemName); foreach (var projectReferenceToParse in projectReferenceItems) { string projectReferenceFullPath = projectReferenceToParse.GetMetadataValue(FullPathMetadataName); PropertyDictionary <ProjectPropertyInstance> projectReferenceGlobalProperties = GetProjectReferenceGlobalProperties(projectReferenceToParse, globalProperties); var projectReferenceConfigurationMetadata = new ConfigurationMetadata(projectReferenceFullPath, projectReferenceGlobalProperties); ProjectGraphNode projectReference = _allParsedProjects[projectReferenceConfigurationMetadata]; if (nodeState.TryGetValue(projectReference, out NodeState 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 == NodeState.InProcess) { if (node.Equals(projectReference)) { // 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)); } else { // 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> { projectReferenceConfigurationMetadata.ProjectFullPath }; return(false, projectsInCycle); } } } else { // recursively process newly discovered references var loadReference = DetectCycles(projectReference, nodeState, projectCollection, projectReferenceGlobalProperties); 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(projectReferenceConfigurationMetadata.ProjectFullPath); loadReference.projectsInCycle.Add(node.ProjectInstance.FullPath); var errorMessage = FormatCircularDependencyError(loadReference.projectsInCycle); throw new CircularDependencyException(string.Format( ResourceUtilities.GetResourceString("CircularDependencyInProjectGraph"), errorMessage)); } else { // 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(projectReferenceConfigurationMetadata.ProjectFullPath); return(false, loadReference.projectsInCycle); } } } ProjectGraphNode parsedProjectReference = _allParsedProjects[projectReferenceConfigurationMetadata]; node.AddProjectReference(parsedProjectReference); parsedProjectReference.AddReferencingProject(node); } nodeState[node] = NodeState.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 LoadGraph( ConcurrentQueue <ConfigurationMetadata> projectsToEvaluate, ProjectCollection projectCollection, ConcurrentDictionary <ConfigurationMetadata, object> tasksInProgress, ProjectInstanceFactoryFunc projectInstanceFactory, 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(() => { ProjectGraphNode parsedProject = CreateNewNode(projectToEvaluate, projectCollection, projectInstanceFactory); IEnumerable <ProjectItemInstance> projectReferenceItems = parsedProject.ProjectInstance.GetItems(MSBuildConstants.ProjectReferenceItemName); foreach (var projectReferenceToParse in projectReferenceItems) { if (!string.IsNullOrEmpty(projectReferenceToParse.GetMetadataValue(ToolsVersionMetadataName))) { throw new InvalidOperationException(string.Format( CultureInfo.InvariantCulture, ResourceUtilities.GetResourceString( "ProjectGraphDoesNotSupportProjectReferenceWithToolset"), projectReferenceToParse.EvaluatedInclude, parsedProject.ProjectInstance.FullPath)); } string projectReferenceFullPath = projectReferenceToParse.GetMetadataValue(FullPathMetadataName); PropertyDictionary <ProjectPropertyInstance> projectReferenceGlobalProperties = GetProjectReferenceGlobalProperties(projectReferenceToParse, projectToEvaluate.GlobalProperties); var projectReferenceConfigurationMetadata = new ConfigurationMetadata(projectReferenceFullPath, projectReferenceGlobalProperties); if (!tasksInProgress.ContainsKey(projectReferenceConfigurationMetadata)) { if (!_allParsedProjects.ContainsKey(projectReferenceConfigurationMetadata)) { projectsToEvaluate.Enqueue(projectReferenceConfigurationMetadata); 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); }
/// <summary> /// Gets the target list to be executed for every project in the graph, given a particular target list for the entry project. /// </summary> /// <remarks> /// This method uses the ProjectReferenceTargets items to determine the targets to run per node. The results can then be used /// to start building each project individually, assuming a given project is built after its references. /// </remarks> /// <param name="entryProjectTargets">The target list for the entry project. May be null or empty, in which case the entry projects' default targets will be used.</param> /// <returns>A dictionary containing the target list for each node.</returns> public IReadOnlyDictionary <ProjectGraphNode, ImmutableList <string> > GetTargetLists(ICollection <string> entryProjectTargets) { // Seed the dictionary with empty lists for every node. In this particular case though an empty list means "build nothing" rather than "default targets". Dictionary <ProjectGraphNode, ImmutableList <string> > targetLists = ProjectNodes.ToDictionary(node => node, node => ImmutableList <string> .Empty); var encounteredEdges = new HashSet <ProjectGraphBuildRequest>(); var edgesToVisit = new Queue <ProjectGraphBuildRequest>(); // Initial state of the graph traversal. foreach (var entryPointNode in EntryPointNodes) { ImmutableList <string> entryTargets = entryProjectTargets == null || entryProjectTargets.Count == 0 ? ImmutableList.CreateRange(entryPointNode.ProjectInstance.DefaultTargets) : ImmutableList.CreateRange(entryProjectTargets); var entryEdge = new ProjectGraphBuildRequest(entryPointNode, entryTargets); encounteredEdges.Add(entryEdge); edgesToVisit.Enqueue(entryEdge); } // Traverse the entire graph, visiting each edge once. while (edgesToVisit.Count > 0) { ProjectGraphBuildRequest buildRequest = edgesToVisit.Dequeue(); ProjectGraphNode node = buildRequest.Node; ImmutableList <string> requestedTargets = buildRequest.RequestedTargets; targetLists[node] = targetLists[node].AddRange(requestedTargets); // No need to continue if this node has no project references. if (node.ProjectReferences.Count == 0) { continue; } // Based on the entry points of this project, determine which targets to propagate down to project references. ImmutableList <string> targetsToPropagate = DetermineTargetsToPropagate(node, requestedTargets); // Queue the project references for visitation, if the edge hasn't already been traversed. foreach (var projectReference in node.ProjectReferences) { var projectReferenceEdge = new ProjectGraphBuildRequest( projectReference, ExpandDefaultTargets(projectReference.ProjectInstance, targetsToPropagate)); if (encounteredEdges.Add(projectReferenceEdge)) { edgesToVisit.Enqueue(projectReferenceEdge); } } } // Dedupe target lists List <KeyValuePair <ProjectGraphNode, ImmutableList <string> > > entriesToUpdate = new List <KeyValuePair <ProjectGraphNode, ImmutableList <string> > >(); foreach (KeyValuePair <ProjectGraphNode, ImmutableList <string> > pair in targetLists) { ImmutableList <string> targetList = pair.Value; SortedSet <string> seenTargets = new SortedSet <string>(StringComparer.OrdinalIgnoreCase); int i = 0; while (i < targetList.Count) { if (seenTargets.Add(targetList[i])) { i++; } else { targetList = targetList.RemoveAt(i); } } // Only update if it changed if (targetList != pair.Value) { entriesToUpdate.Add(new KeyValuePair <ProjectGraphNode, ImmutableList <string> >(pair.Key, targetList)); } } // Update in a separate pass to avoid modifying a collection while iterating it. foreach (KeyValuePair <ProjectGraphNode, ImmutableList <string> > pair in entriesToUpdate) { targetLists[pair.Key] = pair.Value; } return(targetLists); }
/// <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 GraphEdges CreateEdgesAndDetectCycles(Dictionary <ConfigurationMetadata, ProjectGraphNode> allParsedProjects) { var edges = new GraphEdges(); var nodeStates = new Dictionary <ProjectGraphNode, NodeVisitationState>(); foreach (ConfigurationMetadata entryPointConfig in _entryPointConfigurationMetadata) { var entryPointNode = allParsedProjects[entryPointConfig]; if (!nodeStates.ContainsKey(entryPointNode)) { CreateEdgesAndDetectCyclesForRoot(entryPointNode, nodeStates); } else { ErrorUtilities.VerifyThrow( nodeStates[entryPointNode] == NodeVisitationState.Processed, "entrypoints should get processed after a call to detect cycles"); } } return(edges); List <string> CreateEdgesAndDetectCyclesForRoot( ProjectGraphNode node, IDictionary <ProjectGraphNode, NodeVisitationState> nodeState) { nodeState[node] = NodeVisitationState.InProcess; foreach (var(referenceConfig, projectReferenceItem) in _projectInterpretation.GetReferences(node.ProjectInstance)) { ProjectGraphNode 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(projectsInCycle); } } else { // recursively process newly discovered references List <string> projectsInCycle = CreateEdgesAndDetectCyclesForRoot(referenceNode, nodeState); if (projectsInCycle != null) { if (projectsInCycle[0].Equals(node.ProjectInstance.FullPath)) { // we have reached the nth project in the cycle, form error message and throw projectsInCycle.Add(referenceConfig.ProjectFullPath); projectsInCycle.Add(node.ProjectInstance.FullPath); var errorMessage = FormatCircularDependencyError(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 projectsInCycle.Add(referenceConfig.ProjectFullPath); return(projectsInCycle); } } node.AddProjectReference(referenceNode, projectReferenceItem, edges); } nodeState[node] = NodeVisitationState.Processed; return(null); } }
/// <summary> /// Indexer which sets or returns results for the specified node /// </summary> /// <param name="node">The node</param> /// <returns>The results for the specified node</returns> /// <exception>KeyNotFoundException is returned if the specified node doesn't exist when reading this property.</exception> public BuildResult this[ProjectGraphNode node] => ResultsByNode[node];
internal void AddReferencingProject(ProjectGraphNode projectGraphNode) => _referencingProjects.Add(projectGraphNode);
internal void AddProjectReference(ProjectGraphNode projectGraphNode) => _projectReferences.Add(projectGraphNode);
internal void RemoveProjectReference(ProjectGraphNode projectGraphNode) => _projectReferences.Remove(projectGraphNode);