Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/Build.UnitTests/Graph/ProjectGraph_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Tasks;
using Microsoft.Build.UnitTests;
using Shouldly;
using Xunit;
Expand Down Expand Up @@ -3003,6 +3004,64 @@ public void InvalidProjectReferenceErrorIncludesMultipleReferringProjects()
(mentionsProject1 || mentionsProject2).ShouldBeTrue();
}

[Fact]
public void Test1()
{
using var env = TestEnvironment.Create();

TransientTestFile project1 = env.CreateFile("project1.proj", """
<Project>
<PropertyGroup>
<TargetFrameworks>net472;net10.0</TargetFrameworks>
<InnerBuildProperty>TargetFramework</InnerBuildProperty>
<InnerBuildPropertyValues>TargetFrameworks</InnerBuildPropertyValues>
</PropertyGroup>
</Project>
""");

TransientTestFile project2 = env.CreateFile("project2.proj", $"""
<Project>
<PropertyGroup>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="{project1.Path}">
<SetTargetFramework>TargetFramework=net472</SetTargetFramework>
</ProjectReference>
</ItemGroup>
</Project>
""");

var projectGraph = new ProjectGraph(
new ProjectGraphOptions
{
EntryPoints = [new ProjectGraphEntryPoint(project2.Path)],
Mode = ProjectGraphMode.Full
});

var sorted = projectGraph.ProjectNodesTopologicallySorted.ToList();

sorted[0].ProjectInstance.FullPath.ShouldBe(project1.Path);
sorted[0].ProjectInstance.GlobalProperties.Count.ShouldBe(2); // IsGraphBuild=true plus TargetFramework=net10.0 (inner build)
sorted[0].ProjectReferences.Count.ShouldBe(0);
sorted[0].ProjectType.ShouldBe(ProjectInterpretation.ProjectType.InnerBuild);

sorted[1].ProjectInstance.FullPath.ShouldBe(project1.Path);
sorted[1].ProjectInstance.GlobalProperties.Count.ShouldBe(2); // IsGraphBuild=true plus TargetFramework=net472 (inner build)
sorted[1].ProjectReferences.Count.ShouldBe(0);
sorted[1].ProjectType.ShouldBe(ProjectInterpretation.ProjectType.InnerBuild);

sorted[2].ProjectInstance.FullPath.ShouldBe(project1.Path);
sorted[2].ProjectInstance.GlobalProperties.Count.ShouldBe(1); // IsGraphBuild=true (outer build)
sorted[2].ProjectReferences.Count.ShouldBe(2); // Should have project references to the two inner builds
sorted[2].ProjectType.ShouldBe(ProjectInterpretation.ProjectType.OuterBuild);

sorted[3].ProjectInstance.FullPath.ShouldBe(project2.Path);
sorted[3].ProjectInstance.GlobalProperties.Count.ShouldBe(1); // IsGraphBuild=true
sorted[3].ProjectReferences.Count.ShouldBe(3); // Should have project references to the outer build of project1 and the two inner builds
sorted[3].ProjectType.ShouldBe(ProjectInterpretation.ProjectType.NonMultitargeting);
}

public void Dispose()
{
_env.Dispose();
Expand Down
5 changes: 4 additions & 1 deletion src/Build/Graph/GraphBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ internal class GraphBuilder
private readonly ProjectInterpretation _projectInterpretation;

private readonly ProjectGraph.ProjectInstanceFactoryFunc _projectInstanceFactory;
private readonly ProjectGraphMode _graphMode;
private IReadOnlyDictionary<string, IReadOnlyCollection<string>> _solutionDependencies;
private ConcurrentDictionary<ConfigurationMetadata, Lazy<ProjectInstance>> _platformNegotiationInstancesCache = new();

Expand All @@ -67,6 +68,7 @@ public GraphBuilder(
ProjectGraph.ProjectInstanceFactoryFunc projectInstanceFactory,
ProjectInterpretation projectInterpretation,
int degreeOfParallelism,
ProjectGraphMode mode,
CancellationToken cancellationToken)
{
var (actualEntryPoints, solutionDependencies) = ExpandSolutionIfPresent(entryPoints.ToImmutableArray());
Expand All @@ -85,6 +87,7 @@ public GraphBuilder(
_projectCollection = projectCollection;
_projectInstanceFactory = projectInstanceFactory;
_projectInterpretation = projectInterpretation;
_graphMode = mode;
}

public void BuildGraph()
Expand Down Expand Up @@ -617,7 +620,7 @@ private void SubmitProjectForParsing(ConfigurationMetadata projectToEvaluate)
{
var referenceInfos = new List<ProjectInterpretation.ReferenceInfo>();

foreach (var referenceInfo in _projectInterpretation.GetReferences(parsedProject, _projectCollection, GetInstanceForPlatformNegotiationWithCaching))
foreach (var referenceInfo in _projectInterpretation.GetReferences(parsedProject, _projectCollection, GetInstanceForPlatformNegotiationWithCaching, _graphMode))
{
if (FileUtilities.IsSolutionFilename(referenceInfo.ReferenceConfiguration.ProjectFullPath))
{
Expand Down
34 changes: 29 additions & 5 deletions src/Build/Graph/ProjectGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -420,23 +420,47 @@ public ProjectGraph(
ProjectInstanceFactoryFunc projectInstanceFactory,
int degreeOfParallelism,
CancellationToken cancellationToken)
: this(new ProjectGraphOptions
{
EntryPoints = entryPoints,
ProjectCollection = projectCollection,
ProjectInstanceFactoryFunc = projectInstanceFactory,
DegreeOfParallelism = degreeOfParallelism
},
cancellationToken)
{
}

/// <summary>
/// Constructs a graph starting from the given graph options.
/// </summary>
/// <param name="options">A <see cref="ProjectGraphOptions" /> containing the entry projects, project collection, and other details about the graph.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <exception cref="InvalidProjectFileException">If the evaluation of any project in the graph fails.</exception>
/// <exception cref="CircularDependencyException">If the evaluation is successful but the project graph contains a circular dependency.</exception>
public ProjectGraph(
ProjectGraphOptions options,
CancellationToken cancellationToken = default)
{
ErrorUtilities.VerifyThrowArgumentNull(projectCollection);
ErrorUtilities.VerifyThrowArgumentNull(options.ProjectCollection);

var measurementInfo = BeginMeasurement();

ProjectInstanceFactoryFunc projectInstanceFactory = options.ProjectInstanceFactoryFunc;

if (projectInstanceFactory is null)
{
_evaluationContext = EvaluationContext.Create(EvaluationContext.SharingPolicy.Shared);
projectInstanceFactory = DefaultProjectInstanceFactory;
}

var graphBuilder = new GraphBuilder(
entryPoints,
projectCollection,
options.EntryPoints,
options.ProjectCollection,
projectInstanceFactory,
ProjectInterpretation.Instance,
degreeOfParallelism,
options.DegreeOfParallelism,
options.Mode,
cancellationToken);
graphBuilder.BuildGraph();

Expand All @@ -456,7 +480,7 @@ public ProjectGraph(

if (MSBuildEventSource.Log.IsEnabled())
{
etwArgs = string.Join(";", entryPoints.Select(
etwArgs = string.Join(";", options.EntryPoints.Select(
e =>
{
var globalPropertyString = e.GlobalProperties == null
Expand Down
55 changes: 55 additions & 0 deletions src/Build/Graph/ProjectGraphOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using Microsoft.Build.Evaluation;

namespace Microsoft.Build.Graph
{
/// <summary>
/// Represents the mode to use when constructing a <see cref="ProjectGraph"/>.
/// </summary>
public enum ProjectGraphMode
{
/// <summary>
/// Loads only the projects needed for a build, as specified by the entry points. This is the default mode.
/// </summary>
Default = 0,

/// <summary>
/// Loads a complete representation of the graph, even if they are not needed for the build.
/// </summary>
Full = 1,
}

/// <summary>
/// Represents options to use when constructing a <see cref="ProjectGraph" />.
/// </summary>
public readonly struct ProjectGraphOptions()
{
/// <summary>
/// The degree of parallelism to use when constructing the graph. Defaults to the number of logical cores on the machine.
/// </summary>
public int DegreeOfParallelism { get; init; } = NativeMethodsShared.GetLogicalCoreCount();

/// <summary>
/// A list of <see cref="ProjectGraphEntryPoint" /> objects representing the entry points to use when constructing the graph.
/// </summary>
public required IEnumerable<ProjectGraphEntryPoint> EntryPoints { get; init; }

/// <summary>
/// The <see cref="ProjectGraphMode" /> to use when constructing the graph. Defaults to <see cref="ProjectGraphMode.Default" />.
/// </summary>
public ProjectGraphMode Mode { get; init; } = ProjectGraphMode.Default;

/// <summary>
/// The <see cref="ProjectCollection" /> to load projects into when constructing the graph. Defaults to <see cref="ProjectCollection.GlobalProjectCollection"/ >.

Check failure on line 46 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (Source-Build (Managed))

src/Build/Graph/ProjectGraphOptions.cs#L46

src/Build/Graph/ProjectGraphOptions.cs(46,166): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'The character(s) '/' cannot be used at this location.'

Check failure on line 46 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (Linux Core Multithreaded Mode)

src/Build/Graph/ProjectGraphOptions.cs#L46

src/Build/Graph/ProjectGraphOptions.cs(46,166): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'The character(s) '/' cannot be used at this location.'

Check failure on line 46 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (Linux Core Multithreaded Mode)

src/Build/Graph/ProjectGraphOptions.cs#L46

src/Build/Graph/ProjectGraphOptions.cs(46,166): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'The character(s) '/' cannot be used at this location.'

Check failure on line 46 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (Linux Core)

src/Build/Graph/ProjectGraphOptions.cs#L46

src/Build/Graph/ProjectGraphOptions.cs(46,166): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'The character(s) '/' cannot be used at this location.'

Check failure on line 46 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (Linux Core)

src/Build/Graph/ProjectGraphOptions.cs#L46

src/Build/Graph/ProjectGraphOptions.cs(46,166): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'The character(s) '/' cannot be used at this location.'

Check failure on line 46 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (macOS Core)

src/Build/Graph/ProjectGraphOptions.cs#L46

src/Build/Graph/ProjectGraphOptions.cs(46,166): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'The character(s) '/' cannot be used at this location.'

Check failure on line 46 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (macOS Core)

src/Build/Graph/ProjectGraphOptions.cs#L46

src/Build/Graph/ProjectGraphOptions.cs(46,166): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'The character(s) '/' cannot be used at this location.'

Check failure on line 46 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr

src/Build/Graph/ProjectGraphOptions.cs#L46

src/Build/Graph/ProjectGraphOptions.cs(46,166): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'The character(s) '/' cannot be used at this location.'

Check failure on line 46 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr

src/Build/Graph/ProjectGraphOptions.cs#L46

src/Build/Graph/ProjectGraphOptions.cs(46,166): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'The character(s) '/' cannot be used at this location.'
/// </summary>

Check failure on line 47 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (Source-Build (Managed))

src/Build/Graph/ProjectGraphOptions.cs#L47

src/Build/Graph/ProjectGraphOptions.cs(47,15): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'see'.'

Check failure on line 47 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (Linux Core Multithreaded Mode)

src/Build/Graph/ProjectGraphOptions.cs#L47

src/Build/Graph/ProjectGraphOptions.cs(47,15): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'see'.'

Check failure on line 47 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (Linux Core Multithreaded Mode)

src/Build/Graph/ProjectGraphOptions.cs#L47

src/Build/Graph/ProjectGraphOptions.cs(47,15): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'see'.'

Check failure on line 47 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (Linux Core)

src/Build/Graph/ProjectGraphOptions.cs#L47

src/Build/Graph/ProjectGraphOptions.cs(47,15): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'see'.'

Check failure on line 47 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (Linux Core)

src/Build/Graph/ProjectGraphOptions.cs#L47

src/Build/Graph/ProjectGraphOptions.cs(47,15): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'see'.'

Check failure on line 47 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (macOS Core)

src/Build/Graph/ProjectGraphOptions.cs#L47

src/Build/Graph/ProjectGraphOptions.cs(47,15): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'see'.'

Check failure on line 47 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (macOS Core)

src/Build/Graph/ProjectGraphOptions.cs#L47

src/Build/Graph/ProjectGraphOptions.cs(47,15): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'see'.'

Check failure on line 47 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr

src/Build/Graph/ProjectGraphOptions.cs#L47

src/Build/Graph/ProjectGraphOptions.cs(47,15): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'see'.'
public ProjectCollection ProjectCollection { get; init; } = ProjectCollection.GlobalProjectCollection;

Check failure on line 48 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (Source-Build (Managed))

src/Build/Graph/ProjectGraphOptions.cs#L48

src/Build/Graph/ProjectGraphOptions.cs(48,1): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check failure on line 48 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (Linux Core Multithreaded Mode)

src/Build/Graph/ProjectGraphOptions.cs#L48

src/Build/Graph/ProjectGraphOptions.cs(48,1): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check failure on line 48 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (Linux Core)

src/Build/Graph/ProjectGraphOptions.cs#L48

src/Build/Graph/ProjectGraphOptions.cs(48,1): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check failure on line 48 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr (macOS Core)

src/Build/Graph/ProjectGraphOptions.cs#L48

src/Build/Graph/ProjectGraphOptions.cs(48,1): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check failure on line 48 in src/Build/Graph/ProjectGraphOptions.cs

View check run for this annotation

Azure Pipelines / msbuild-pr

src/Build/Graph/ProjectGraphOptions.cs#L48

src/Build/Graph/ProjectGraphOptions.cs(48,1): error CS1570: (NETCORE_ENGINEERING_TELEMETRY=Build) XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

/// <summary>
/// An optional <see cref="ProjectInstanceFactoryFunc" /> to use when evaluating individual projects in the graph.
/// </summary>
public ProjectGraph.ProjectInstanceFactoryFunc? ProjectInstanceFactoryFunc { get; init; }
}
}
52 changes: 51 additions & 1 deletion src/Build/Graph/ProjectInterpretation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public TargetSpecification(string target, bool skipIfNonexistent)
public bool SkipIfNonexistent { get; }
}

public IEnumerable<ReferenceInfo> GetReferences(ProjectGraphNode projectGraphNode, ProjectCollection projectCollection, ProjectGraph.ProjectInstanceFactoryFunc projectInstanceFactory)
public IEnumerable<ReferenceInfo> GetReferences(ProjectGraphNode projectGraphNode, ProjectCollection projectCollection, ProjectGraph.ProjectInstanceFactoryFunc projectInstanceFactory, ProjectGraphMode graphMode)
{
IEnumerable<ProjectItemInstance> projectReferenceItems;
IEnumerable<GlobalPropertiesModifier> globalPropertiesModifiers = null;
Expand Down Expand Up @@ -198,6 +198,56 @@ public IEnumerable<ReferenceInfo> GetReferences(ProjectGraphNode projectGraphNod

var referenceConfig = new ConfigurationMetadata(projectReferenceFullPath, referenceGlobalProperties);

// When in Full graph mode, enumerate all target frameworks regardless of SetTargetFramework
if (graphMode == ProjectGraphMode.Full && projectReferenceItem.HasMetadata(SetTargetFrameworkMetadataName))
{
// Evaluate the referenced project with no global properties to discover all its target frameworks
var projectInstanceForDiscovery = projectInstanceFactory(
projectReferenceFullPath,
null,
projectCollection);

string targetFrameworksValue = projectInstanceForDiscovery.GetPropertyValue(PropertyNames.TargetFrameworks);

// If the project has multiple target frameworks, yield the outer build only
// The normal ConstructInnerBuildReferences logic will discover and create all inner builds
if (!string.IsNullOrWhiteSpace(targetFrameworksValue))
{
// Create base properties without SetTargetFramework constraint
// GetGlobalPropertiesForItem processes SetTargetFramework and adds TargetFramework to properties,
// so we need to create properties without that processing
var baseProperties = GetGlobalPropertiesForItem(
projectReferenceItem,
requesterInstance.GlobalPropertiesDictionary,
allowCollectionReuse: false,
// Use empty list to skip ProjectReferenceGlobalPropertiesModifier which processes SetTargetFramework
[]);

// Yield only the outer build (no TargetFramework override)
// The ConstructInnerBuildReferences logic will discover and create the inner builds
yield return new ReferenceInfo(new ConfigurationMetadata(projectReferenceFullPath, baseProperties), projectReferenceItem);

yield break;
}
else
{
string targetFrameworkValue = projectInstanceForDiscovery.GetPropertyValue(PropertyNames.TargetFramework);

// Single target framework project - just yield it as is without SetTargetFramework constraint
if (!string.IsNullOrWhiteSpace(targetFrameworkValue))
{
var baseProperties = GetGlobalPropertiesForItem(
projectReferenceItem,
requesterInstance.GlobalPropertiesDictionary,
allowCollectionReuse: false,
// Use empty list to skip SetTargetFramework processing
[]);
yield return new ReferenceInfo(new ConfigurationMetadata(projectReferenceFullPath, baseProperties), projectReferenceItem);
yield break;
}
}
}

yield return new ReferenceInfo(referenceConfig, projectReferenceItem);

static void SetProperty(PropertyDictionary<ProjectPropertyInstance> properties, string propertyName, string propertyValue)
Expand Down
1 change: 1 addition & 0 deletions src/Build/Microsoft.Build.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@
<Compile Include="Evaluation\PropertyTrackingEvaluatorDataWrapper.cs" />
<Compile Include="Graph\GraphBuilder.cs" />
<Compile Include="Graph\ParallelWorkSet.cs" />
<Compile Include="Graph\ProjectGraphOptions.cs" />
<Compile Include="Graph\ProjectInterpretation.cs" />
<Compile Include="Graph\GraphBuildResult.cs" />
<Compile Include="Graph\GraphBuildSubmission.cs" />
Expand Down
Loading