Skip to content

Rewrite resolveprojectreferences MSBuild calls to reduce the amount of batching done#12631

Open
baronfel wants to merge 5 commits intomainfrom
optimize-ResolveProjectReferences-MSBuild-Task-batching
Open

Rewrite resolveprojectreferences MSBuild calls to reduce the amount of batching done#12631
baronfel wants to merge 5 commits intomainfrom
optimize-ResolveProjectReferences-MSBuild-Task-batching

Conversation

@baronfel
Copy link
Copy Markdown
Member

Context

When writing microsoft/vstest#15295, @rainersigwald pointed out to me that we suffer from the same anti-pattern with regards to usage of the MSBuild Task in ResolveProjectReferences, which is a very-commonly-called Target.

When batching is used directly in the MSBuild Task call, it can lead to repeated invocations of the Task, which have small but noticeable overheads. The fix is to push as much information about the customization of the individual Project builds being requested as possible into the Item descriptions of the projects themselves - we do this by precomputing a few item lists, one for each MSBuild call section.

This may increase overall memory usage, so it's worth measuring against the reduction in overhead from the batched Task calls. In my investigations of a recent partner team build, it's very common to end up with at least 2 batches per logical Task request in this particular Target:

image

Changes Made

Precompute Project Item lists for each of the three steps of ResolveProjectReference:

  • GetTargetPath
  • default or explicitly-specified build targets
  • GetNativeManifest

Testing

None additional, as this should be a not-publicly-visible change.

Notes

@baronfel baronfel marked this pull request as ready for review January 5, 2026 23:21
Copilot AI review requested due to automatic review settings January 5, 2026 23:21
@baronfel baronfel self-assigned this Jan 5, 2026
@baronfel baronfel added the Area: Tasks Issues impacting the tasks shipped in Microsoft.Build.Tasks.Core.dll. label Jan 5, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR attempts to optimize the ResolveProjectReferences target by reducing MSBuild task batching overhead. Instead of using batching directly in the MSBuild task calls (which can result in multiple task invocations), the PR precomputes three item lists (_ProjectsForGetTargetPath, _ProjectsForCommandLineBuild, _ProjectsForNativeManifest) with all necessary metadata attached, allowing each MSBuild task to be called just once per logical build phase.

However, the PR contains several critical bugs that prevent it from compiling and would cause runtime failures:

  • Compilation error: The new ExpandItemList method is declared as static but attempts to access the instance property TargetAndPropertyListSeparators, which is a compile-time error in C#
  • Critical logic bug: The UndefineProperties handling was incorrectly changed, causing project-specific properties to be silently dropped when global properties are also specified
  • Critical syntax bug: RemoveProperties concatenation was changed from direct string concatenation to semicolon-separated concatenation, breaking the expected format
  • Moderate bug: Dictionary capacity calculation has an operator precedence error

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 11 comments.

File Description
src/Tasks/Microsoft.Common.CurrentVersion.targets Restructures ResolveProjectReferences target to precompute item lists, reducing batching; contains critical RemoveProperties concatenation bugs
src/Tasks/MSBuild.cs Adds ExpandItemList helper method and refactors property/target parsing; contains critical compilation error and logic bugs
src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs Parallel changes to MSBuild.cs for intrinsic task implementation; contains same critical bugs
Comments suppressed due to low confidence (4)

src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs:484

                foreach (string p in Properties)
                {
                    // Split each property according to the separators
                    string[] expandedPropertyValues = ExpandItemList(p);
                    // Add the resultant properties to the final list
                    foreach (string property in expandedPropertyValues)
                    {
                        expandedProperties.Add(property);
                    }
                }

src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs:501

                foreach (string t in Targets)
                {
                    // Split each target according to the separators
                    string[] expandedTargetValues = ExpandItemList(t);
                    // Add the resultant targets to the final list
                    foreach (string target in expandedTargetValues)
                    {
                        expandedTargets.Add(target);
                    }
                }

src/Tasks/MSBuild.cs:439

                foreach (string t in Properties)
                {
                    // Split each property according to the separators
                    string[] expandedPropertyValues = ExpandItemList(t);

                    // Add the resultant properties to the final list
                    foreach (string property in expandedPropertyValues)
                    {
                        expandedProperties.Add(property);
                    }
                }

src/Tasks/MSBuild.cs:457

                foreach (string t in Targets)
                {
                    // Split each target according to the separators
                    string[] expandedTargetValues = ExpandItemList(t);

                    // Add the resultant targets to the final list
                    foreach (string target in expandedTargetValues)
                    {
                        expandedTargets.Add(target);
                    }
                }

Comment thread src/Tasks/Microsoft.Common.CurrentVersion.targets
Comment thread src/Tasks/MSBuild.cs Outdated
Comment thread src/Tasks/MSBuild.cs Outdated
Comment thread src/Tasks/MSBuild.cs
Comment thread src/Tasks/Microsoft.Common.CurrentVersion.targets
Comment thread src/Tasks/Microsoft.Common.CurrentVersion.targets
Comment thread src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs Outdated
Comment thread src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs Outdated
Comment thread src/Tasks/MSBuild.cs Outdated
@baronfel baronfel force-pushed the optimize-ResolveProjectReferences-MSBuild-Task-batching branch 3 times, most recently from c895e75 to 7e0b7c9 Compare January 6, 2026 15:43
@baronfel baronfel requested a review from a team January 6, 2026 16:42
@ViktorHofer
Copy link
Copy Markdown
Member

This makes sense to me. I noticed the same pattern a while ago but thought that it was intentional.

Comment on lines +2131 to +2136
<ItemGroup>
<_ProjectsForGetTargetPath Include="@(_MSBuildProjectReferenceExistent)"
Condition="'%(_MSBuildProjectReferenceExistent.BuildReference)' == 'true' and '@(ProjectReferenceWithConfiguration)' != '' and ('$(BuildingInsideVisualStudio)' == 'true' or '$(BuildProjectReferences)' != 'true') and '$(VisualStudioVersion)' != '10.0' and '@(_MSBuildProjectReferenceExistent)' != ''"
Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration);%(_MSBuildProjectReferenceExistent.SetPlatform);%(_MSBuildProjectReferenceExistent.SetTargetFramework)"
RemoveProperties="%(_MSBuildProjectReferenceExistent.GlobalPropertiesToRemove);$(_GlobalPropertiesToRemoveFromProjectReferences)" />
</ItemGroup>
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes to this file are the actual operational fixes - creating the Items ahead of time so that only one invocation of each MSBuild Task below fires. You should see this 'create then execute' pattern once for each step of the ResolveProjectReferences Target.

However, this pre-computation creates Items with Metadata that are present and empty (or effectively empty, for example ;; is an empty list when expanded), but the current per-project-Metadata-processing routines in the MSBuild Task implementations assumed that any non-null-or-empty string meant that there would be values to overwrite, so this refactoring resulted in a lot of additional chatter in the binlogs.

Because of this, I also had to go update the MSBuild Task invocations to check if the values of the metadata were effectively empty or not before doing any processing or logging.

Comment thread src/Tasks/MSBuild.cs
Comment on lines +219 to +220
string[] undefinePropertiesArray = ExpandItemList(TargetAndPropertyListSeparators ?? SemicolonSeparatorArray, RemoveProperties);
if (undefinePropertiesArray.Length > 0)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the changes to metadata handling are because of the presence of the not-null-but-semantically-empty metadata values. In each case we expand them out and trim empty values and do comparisons on that list to determine if any modifications need to be made. And because TargetAndPropertyListSeparators is an instance value we can't reference it directly from ExpandItemList, etc etc.

@baronfel baronfel enabled auto-merge (squash) January 7, 2026 02:29
@baronfel baronfel force-pushed the optimize-ResolveProjectReferences-MSBuild-Task-batching branch from 7e0b7c9 to 05e19c6 Compare February 10, 2026 18:38
@github-actions
Copy link
Copy Markdown
Contributor

This pull request has been automatically closed because it has been open for more than 180 days with no recent activity.

If you believe this work is still relevant, please feel free to reopen or create a new pull request. Thank you for your contribution!

Note

🔒 Integrity filter blocked 39 items

The following items were blocked because they don't meet the GitHub integrity level.

  • #13528 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13527 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13523 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13516 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13511 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13495 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13488 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13486 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13480 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13477 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13474 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13458 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13457 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13442 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13429 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13428 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • ... and 23 more items

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by Close Stale Pull Requests · ● 844.8K

@github-actions github-actions Bot closed this Apr 13, 2026
auto-merge was automatically disabled April 13, 2026 11:52

Pull request was closed

@baronfel baronfel reopened this Apr 13, 2026
@github-actions
Copy link
Copy Markdown
Contributor

This pull request has been automatically closed because it has been open for more than 180 days with no recent activity.

If you believe this work is still relevant, please feel free to reopen or create a new pull request. Thank you for your contribution!

Note

🔒 Integrity filter blocked 43 items

The following items were blocked because they don't meet the GitHub integrity level.

  • #12231 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #12868 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #12991 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #12994 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13027 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13094 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13177 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13222 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13250 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13267 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13269 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13273 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13285 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13288 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13319 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #13330 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • ... and 27 more items

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by Close Stale Pull Requests · ● 963.5K

@github-actions github-actions Bot closed this Apr 20, 2026
@baronfel baronfel reopened this Apr 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Tasks Issues impacting the tasks shipped in Microsoft.Build.Tasks.Core.dll.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants