示例#1
0
        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;
示例#2
0
        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);
        }
示例#3
0
 private void ParseReferences(ProjectGraphNode parsedProject)
 {
     foreach ((ConfigurationMetadata referenceConfig, _) in _projectInterpretation.GetReferences(parsedProject.ProjectInstance))
     {
         ParseProject(referenceConfig);
     }
 }
示例#4
0
        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);
        }
示例#5
0
        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));
        }
示例#6
0
        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);
        }
示例#7
0
 public ProjectItemInstance this[(ProjectGraphNode node, ProjectGraphNode reference)key]
示例#8
0
 public ProjectGraphBuildRequest(ProjectGraphNode node, ImmutableList <string> targets)
 {
     Node             = node ?? throw new ArgumentNullException(nameof(node));
     RequestedTargets = targets ?? throw new ArgumentNullException(nameof(targets));
 }
示例#9
0
        /// <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);
        }
示例#10
0
        /// <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);
        }
示例#11
0
        /// <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);
        }
示例#12
0
        /// <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);
            }
        }
示例#13
0
 /// <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];
示例#14
0
 internal void AddReferencingProject(ProjectGraphNode projectGraphNode) => _referencingProjects.Add(projectGraphNode);
示例#15
0
 internal void AddProjectReference(ProjectGraphNode projectGraphNode) => _projectReferences.Add(projectGraphNode);
示例#16
0
 internal void RemoveProjectReference(ProjectGraphNode projectGraphNode) => _projectReferences.Remove(projectGraphNode);