/// <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)); 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)); } }
/// <summary> /// To avoid calling nuget at graph construction time, the graph is initially constructed with outer build nodes referencing inner build nodes. /// However, at build time, for non root outer builds, the inner builds are NOT referenced by the outer build, but by the nodes referencing the /// outer build. Change the graph to mimic this behaviour. /// Examples /// OuterAsRoot -> Inner stays the same /// Node -> Outer -> Inner goes to: Node -> Outer; Node->Inner; Outer -> empty /// </summary> public void PostProcess(Dictionary <ConfigurationMetadata, ParsedProject> allNodes, GraphBuilder graphBuilder) { foreach (var node in allNodes) { var outerBuild = node.Value.GraphNode; if (GetProjectType(outerBuild.ProjectInstance) == ProjectType.OuterBuild && outerBuild.ReferencingProjects.Count != 0) { foreach (var innerBuild in outerBuild.ProjectReferences) { foreach (var outerBuildReferencingProject in outerBuild.ReferencingProjects) { // Which edge should be used to connect the outerBuildReferencingProject to the inner builds? // Decided to use the outerBuildBuildReferencingProject -> outerBuild edge in order to preserve any extra metadata // information that may be present on the edge, like the "Targets" metadata which specifies what // targets to call on the references. var newInnerBuildEdge = graphBuilder.Edges[(outerBuildReferencingProject, outerBuild)];