Ejemplo n.º 1
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);
        }
Ejemplo n.º 2
0
        /// <summary>
        ///     Constructs a graph starting from the given graph entry points, evaluating with the provided project collection.
        /// </summary>
        /// <param name="entryPoints">The entry points to use in constructing the graph</param>
        /// <param name="projectCollection">
        ///     The collection with which all projects in the graph should be associated. May not be
        ///     null.
        /// </param>
        /// <param name="projectInstanceFactory">
        ///     A delegate used for constructing a <see cref="ProjectInstance" />, called for each
        ///     project created during graph creation. This value can be null, which uses
        ///     a default implementation that calls the ProjectInstance constructor. See the remarks
        ///     on <see cref="ProjectInstanceFactoryFunc" /> for other scenarios.
        /// </param>
        /// <param name="degreeOfParallelism">
        ///     Number of threads to participate in building the project graph.
        /// </param>
        /// <param name="cancellationToken">
        ///     The <see cref="T:System.Threading.CancellationToken" /> token to observe.
        /// </param>
        /// <exception cref="InvalidProjectFileException">
        ///     If the evaluation of any project in the graph fails
        /// </exception>
        /// <exception cref="InvalidOperationException">
        ///     If a null reference is returned from <paramref name="projectInstanceFactory" />
        /// </exception>
        /// <exception cref="CircularDependencyException">
        ///     If the evaluation is successful but the project graph contains a circular
        ///     dependency
        /// </exception>
        public ProjectGraph(
            IEnumerable <ProjectGraphEntryPoint> entryPoints,
            ProjectCollection projectCollection,
            ProjectInstanceFactoryFunc projectInstanceFactory,
            int degreeOfParallelism,
            CancellationToken cancellationToken)
        {
            ErrorUtilities.VerifyThrowArgumentNull(projectCollection, nameof(projectCollection));

            projectInstanceFactory = projectInstanceFactory ?? DefaultProjectInstanceFactory;

            var graphBuilder = new GraphBuilder(
                entryPoints,
                projectCollection,
                projectInstanceFactory,
                ProjectInterpretation.Instance,
                degreeOfParallelism,
                cancellationToken);

            graphBuilder.BuildGraph();

            EntryPointNodes = graphBuilder.EntryPointNodes;
            GraphRoots      = graphBuilder.RootNodes;
            ProjectNodes    = graphBuilder.ProjectNodes;
            Edges           = graphBuilder.Edges;

            _projectNodesTopologicallySorted = new Lazy <IReadOnlyCollection <ProjectGraphNode> >(() => TopologicalSort(GraphRoots, ProjectNodes));
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Constructs a graph starting from the given graph entry points, evaluating with the provided project collection.
        /// </summary>
        /// <param name="entryPoints">The entry points to use in constructing the graph</param>
        /// <param name="projectCollection">The collection with which all projects in the graph should be associated. May not be null.</param>
        /// <param name="projectInstanceFactory">
        /// A delegate used for constructing a <see cref="ProjectInstance"/>, called for each
        /// project created during graph creation. This value can be null, which uses
        /// a default implementation that calls the ProjectInstance constructor. See the remarks
        /// on <see cref="ProjectInstanceFactoryFunc"/> for other scenarios.
        /// </param>
        /// <exception cref="AggregateException">If the evaluation of any project in the graph fails, the InnerException contains <see cref="InvalidProjectFileException"/>
        /// If a null reference is returned from <paramref name="projectInstanceFactory"/>, the InnerException contains <see cref="InvalidOperationException"/></exception>
        /// <exception cref="CircularDependencyException">If the evaluation is successful but the project graph contains a circular dependency</exception>
        public ProjectGraph(
            IEnumerable <ProjectGraphEntryPoint> entryPoints,
            ProjectCollection projectCollection,
            ProjectInstanceFactoryFunc projectInstanceFactory)
        {
            ErrorUtilities.VerifyThrowArgumentNull(projectCollection, nameof(projectCollection));

            projectInstanceFactory = projectInstanceFactory ?? DefaultProjectInstanceFactory;

            var nodeStates         = new Dictionary <ProjectGraphNode, NodeState>();
            var entryPointNodes    = new List <ProjectGraphNode>();
            var tasksInProgress    = new ConcurrentDictionary <ConfigurationMetadata, object>();
            var projectsToEvaluate = new ConcurrentQueue <ConfigurationMetadata>();
            var entryPointConfigurationMetadata = new List <ConfigurationMetadata>();

            foreach (var entryPoint in entryPoints)
            {
                PropertyDictionary <ProjectPropertyInstance> globalPropertyDictionary = CreatePropertyDictionary(entryPoint.GlobalProperties);
                var configurationMetadata = new ConfigurationMetadata(FileUtilities.NormalizePath(entryPoint.ProjectFile), globalPropertyDictionary);
                projectsToEvaluate.Enqueue(configurationMetadata);
                entryPointConfigurationMetadata.Add(configurationMetadata);
            }

            if (LoadGraph(projectsToEvaluate, projectCollection, tasksInProgress, projectInstanceFactory, out List <Exception> exceptions))
            {
                foreach (var configurationMetadata in entryPointConfigurationMetadata)
                {
                    entryPointNodes.Add(_allParsedProjects[configurationMetadata]);
                    if (!nodeStates.TryGetValue(_allParsedProjects[configurationMetadata], out var _))
                    {
                        DetectCycles(_allParsedProjects[configurationMetadata], nodeStates, projectCollection, configurationMetadata.GlobalProperties);
                    }
                }

                var graphRoots = new List <ProjectGraphNode>(entryPointNodes.Count);
                foreach (var entryPointNode in entryPointNodes)
                {
                    if (entryPointNode.ReferencingProjects.Count == 0)
                    {
                        graphRoots.Add(entryPointNode);
                    }
                }

                EntryPointNodes = entryPointNodes.AsReadOnly();
                ProjectNodes    = _allParsedProjects.Values.ToList();
                GraphRoots      = graphRoots.AsReadOnly();

                _projectNodesTopologicallySorted = new Lazy <IReadOnlyCollection <ProjectGraphNode> >(() => TopologicalSort(GraphRoots, ProjectNodes));
            }
            else
            {
                throw new AggregateException(exceptions);
            }
        }
Ejemplo n.º 4
0
 public ProjectGraph(
     IEnumerable <ProjectGraphEntryPoint> entryPoints,
     ProjectCollection projectCollection,
     ProjectInstanceFactoryFunc projectInstanceFactory)
     : this(
         entryPoints,
         projectCollection,
         projectInstanceFactory,
         NativeMethodsShared.GetLogicalCoreCount(),
         CancellationToken.None)
 {
 }
Ejemplo n.º 5
0
        (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);
            }
        }
Ejemplo n.º 6
0
 /// <summary>
 ///     Constructs a graph starting from the given graph entry points, evaluating with the provided project collection.
 /// </summary>
 /// <param name="entryPoints">The entry points to use in constructing the graph</param>
 /// <param name="projectCollection">
 ///     The collection with which all projects in the graph should be associated. May not be
 ///     null.
 /// </param>
 /// <param name="projectInstanceFactory">
 ///     A delegate used for constructing a <see cref="ProjectInstance" />, called for each
 ///     project created during graph creation. This value can be null, which uses
 ///     a default implementation that calls the ProjectInstance constructor. See the remarks
 ///     on <see cref="ProjectInstanceFactoryFunc" /> for other scenarios.
 /// </param>
 /// <exception cref="InvalidProjectFileException">
 ///     If the evaluation of any project in the graph fails
 /// </exception>
 /// <exception cref="InvalidOperationException">
 ///     If a null reference is returned from <paramref name="projectInstanceFactory" />
 /// </exception>
 /// <exception cref="CircularDependencyException">
 ///     If the evaluation is successful but the project graph contains a circular
 ///     dependency
 /// </exception>
 public ProjectGraph(
     IEnumerable <ProjectGraphEntryPoint> entryPoints,
     ProjectCollection projectCollection,
     ProjectInstanceFactoryFunc projectInstanceFactory)
     : this(
         entryPoints,
         projectCollection,
         projectInstanceFactory,
         Environment.ProcessorCount,
         CancellationToken.None)
 {
 }
Ejemplo n.º 7
0
        /// <summary>
        /// Constructs a graph starting from the given graph entry points, evaluating with the provided project collection.
        /// </summary>
        /// <param name="entryPoints">The entry points to use in constructing the graph</param>
        /// <param name="projectCollection">The collection with which all projects in the graph should be associated. May not be null.</param>
        /// <param name="projectInstanceFactory">
        /// A delegate used for constructing a <see cref="ProjectInstance"/>, called for each
        /// project created during graph creation. This value can be null, which uses
        /// a default implementation that calls the ProjectInstance constructor. See the remarks
        /// on <see cref="ProjectInstanceFactoryFunc"/> for other scenarios.
        /// </param>
        /// <exception cref="AggregateException">If the evaluation of any project in the graph fails, the InnerException contains <see cref="InvalidProjectFileException"/>
        /// If a null reference is returned from <paramref name="projectInstanceFactory"/>, the InnerException contains <see cref="InvalidOperationException"/></exception>
        /// <exception cref="CircularDependencyException">If the evaluation is successful but the project graph contains a circular dependency</exception>
        public ProjectGraph(
            IEnumerable <ProjectGraphEntryPoint> entryPoints,
            ProjectCollection projectCollection,
            ProjectInstanceFactoryFunc projectInstanceFactory)
        {
            ErrorUtilities.VerifyThrowArgumentNull(projectCollection, nameof(projectCollection));

            projectInstanceFactory = projectInstanceFactory ?? DefaultProjectInstanceFactory;

            _projectInterpretation = ProjectInterpretation.Instance;

            var entryPointConfigurationMetadata = new List <ConfigurationMetadata>();

            foreach (var entryPoint in entryPoints)
            {
                var globalPropertyDictionary = CreatePropertyDictionary(entryPoint.GlobalProperties);

                AddGraphBuildGlobalVariable(globalPropertyDictionary);

                var configurationMetadata = new ConfigurationMetadata(FileUtilities.NormalizePath(entryPoint.ProjectFile), globalPropertyDictionary);
                entryPointConfigurationMetadata.Add(configurationMetadata);
            }

            var(entryPointNodes, rootNodes, allNodes) = LoadGraph(entryPointConfigurationMetadata, projectCollection, projectInstanceFactory, _projectInterpretation);

            EntryPointNodes = entryPointNodes;
            GraphRoots      = rootNodes;
            ProjectNodes    = allNodes;

            _projectNodesTopologicallySorted = new Lazy <IReadOnlyCollection <ProjectGraphNode> >(() => TopologicalSort(GraphRoots, ProjectNodes));

            void AddGraphBuildGlobalVariable(PropertyDictionary <ProjectPropertyInstance> globalPropertyDictionary)
            {
                if (globalPropertyDictionary.GetProperty(PropertyNames.IsGraphBuild) == null)
                {
                    globalPropertyDictionary[PropertyNames.IsGraphBuild] = ProjectPropertyInstance.Create(PropertyNames.IsGraphBuild, "true");
                }
            }
        }
Ejemplo n.º 8
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);
        }
Ejemplo n.º 9
0
 /// <summary>
 /// Constructs a graph starting from the given project file, evaluating with the global project collection and no global properties.
 /// </summary>
 /// <param name="entryProjectFile">The project file to use as the entry point in constructing the graph</param>
 /// <param name="projectCollection">The collection with which all projects in the graph should be associated. May not be null.</param>
 /// <param name="projectInstanceFactory">
 /// A delegate used for constructing a <see cref="ProjectInstance"/>, called for each
 /// project created during graph creation. This value can be null, which uses
 /// a default implementation that calls the ProjectInstance constructor. See the remarks
 /// on the <see cref="ProjectInstanceFactoryFunc"/> for other scenarios.
 /// </param>
 /// <exception cref="AggregateException">If the evaluation of any project in the graph fails, the InnerException contains <see cref="InvalidProjectFileException"/>
 /// If a null reference is returned from <paramref name="projectInstanceFactory"/>, the InnerException contains <see cref="InvalidOperationException"/></exception>
 /// </exception>
 public ProjectGraph(string entryProjectFile, ProjectCollection projectCollection, ProjectInstanceFactoryFunc projectInstanceFactory)
     : this(new ProjectGraphEntryPoint(entryProjectFile).AsEnumerable(), projectCollection, projectInstanceFactory)
 {
 }
Ejemplo n.º 10
0
        public ProjectGraph(
            IEnumerable <ProjectGraphEntryPoint> entryPoints,
            ProjectCollection projectCollection,
            ProjectInstanceFactoryFunc projectInstanceFactory,
            int degreeOfParallelism,
            CancellationToken cancellationToken)
        {
            ErrorUtilities.VerifyThrowArgumentNull(projectCollection, nameof(projectCollection));

            var measurementInfo = BeginMeasurement();

            projectInstanceFactory ??= DefaultProjectInstanceFactory;

            var graphBuilder = new GraphBuilder(
                entryPoints,
                projectCollection,
                projectInstanceFactory,
                ProjectInterpretation.Instance,
                degreeOfParallelism,
                cancellationToken);

            graphBuilder.BuildGraph();

            EntryPointNodes = graphBuilder.EntryPointNodes;
            GraphRoots      = graphBuilder.RootNodes;
            ProjectNodes    = graphBuilder.ProjectNodes;
            Edges           = graphBuilder.Edges;

            _projectNodesTopologicallySorted = new Lazy <IReadOnlyCollection <ProjectGraphNode> >(() => TopologicalSort(GraphRoots, ProjectNodes));

            ConstructionMetrics = EndMeasurement();

            (Stopwatch Timer, string ETWArgs) BeginMeasurement()
            {
                string etwArgs = null;

                if (MSBuildEventSource.Log.IsEnabled())
                {
                    etwArgs = string.Join(";", entryPoints.Select(
                                              e =>
                    {
                        var globalPropertyString = e.GlobalProperties == null
                                ? string.Empty
                                : string.Join(", ", e.GlobalProperties.Select(kvp => $"{kvp.Key} = {kvp.Value}"));

                        return($"{e.ProjectFile}({globalPropertyString})");
                    }));

                    MSBuildEventSource.Log.ProjectGraphConstructionStart(etwArgs);
                }

                return(Stopwatch.StartNew(), etwArgs);
            }

            GraphConstructionMetrics EndMeasurement()
            {
                if (MSBuildEventSource.Log.IsEnabled())
                {
                    MSBuildEventSource.Log.ProjectGraphConstructionStop(measurementInfo.ETWArgs);
                }

                measurementInfo.Timer.Stop();

                return(new GraphConstructionMetrics(
                           measurementInfo.Timer.Elapsed,
                           ProjectNodes.Count,
                           Edges.Count));
            }
        }
Ejemplo n.º 11
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 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);
        }