Fix RecursiveDir metadata loss in multithreaded builds#13142
Merged
JanProvaznik merged 4 commits intodotnet:mainfrom Feb 9, 2026
Merged
Fix RecursiveDir metadata loss in multithreaded builds#13142JanProvaznik merged 4 commits intodotnet:mainfrom
JanProvaznik merged 4 commits intodotnet:mainfrom
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes loss of %(RecursiveDir) when items cross the TaskHost process boundary during multithreaded builds, preventing directory flattening in scenarios like dotnet pack.
Changes:
- Preserve
RecursiveDirby copying it into custom metadata duringTaskParameterTaskItemconstruction for TaskHost serialization. - Preserve
ProjectItemInstance.TaskItem’s_projectDirectoryacross cloning and serialization. - Ensure
TaskItemFactory.CreateItem(string, string, string)preservesincludeBeforeWildcardExpansionEscaped(needed for correctRecursiveDirsemantics).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/Shared/TaskParameter.cs | Copies RecursiveDir into custom metadata so it survives TaskHost serialization. |
| src/Build/Instance/ProjectItemInstance.cs | Preserves _projectDirectory across clone/serialization; fixes task item creation to keep pre-wildcard include. |
Member
|
Oh wow - I think I got this in the RID-specific tool packaging work and worked around it without understanding what was happening. Really great identification of the problem here! |
675e5d0 to
f1be335
Compare
When items were returned from MSBuild tasks (like the MSBuild task's TargetOutputs) or passed to tasks via output parameters, the %(RecursiveDir) built-in metadata was lost. This caused NuGet pack to flatten directory structures (e.g., build\wix\bundle\bundle.wxs became build\bundle.wxs), resulting in NU5118 errors. Root causes found and fixed: 1. TaskExecutionHost.cs: When creating ProjectItemInstance from task outputs, only IncludeEscaped was passed to the constructor, losing IncludeBeforeWildcardExpansionEscaped which is required for RecursiveDir. Fix: Use the 5-parameter constructor that accepts both values. 2. TaskParameter.cs: TaskParameterTaskItem only copied custom metadata when wrapping ITaskItem for TaskHost serialization, but RecursiveDir is a built-in metadata computed from _includeBeforeWildcardExpansionEscaped. Fix: Explicitly copy RecursiveDir to custom metadata so it survives cross-process serialization. Added ContainsKey check before calling GetMetadataValueEscaped to avoid expensive FileMatcher calls. 3. ProjectItemInstance.cs: Related fixes to preserve _projectDirectory in copy constructor, serialization, and CreateItem methods. The CreateItem method now derives projectDirectory from definingProject. Fixes dotnet#3121
f1be335 to
f17258b
Compare
JanProvaznik
commented
Jan 30, 2026
Member
Author
|
looking for 2 reviewers here |
ViktorHofer
reviewed
Feb 4, 2026
ViktorHofer
approved these changes
Feb 4, 2026
rainersigwald
approved these changes
Feb 8, 2026
Member
rainersigwald
left a comment
There was a problem hiding this comment.
Feels like we should have an end-to-end test pinning the fix too.
JanProvaznik
added a commit
to dotnet/dotnet
that referenced
this pull request
Feb 11, 2026
MSBuild now preserves RecursiveDir across task boundaries (dotnet/msbuild#13142). Some targets (e.g. sharedfx.targets) bake RecursiveDir into TargetPath/PackagePath via %(RecursiveDir), relying on MSBuild previously losing it after the task boundary. With the fix, NuGet Pack would append RecursiveDir again, doubling the path (e.g. analyzers/dotnet/cs/dotnet/cs/ instead of analyzers/dotnet/cs/). Add an EndsWith guard to skip appending RecursiveDir when PackagePath already contains it. This is backward-compatible with both old and new MSBuild.
JanProvaznik
added a commit
that referenced
this pull request
Feb 12, 2026
This reverts commit eff1a3b.
Copilot AI
pushed a commit
that referenced
this pull request
Feb 17, 2026
When using MSBuild's multithreaded mode (-mt / /maxcpucount), the %(RecursiveDir) built-in metadata was lost when items crossed process boundaries to the TaskHost. This caused NuGet pack to flatten directory structures (e.g., build\wix\bundle\bundle.wxs became build\bundle.wxs). Root cause: TaskParameterTaskItem only copied custom metadata when wrapping ITaskItem for TaskHost serialization. RecursiveDir is a built-in metadata that cannot be derived from just the item spec - it requires the original wildcard pattern (_includeBeforeWildcardExpansionEscaped). Fix: Explicitly copy RecursiveDir to custom metadata in TaskParameterTaskItem constructor before serialization, so it survives the cross-process boundary. Also includes related fixes to ProjectItemInstance.cs: - Preserve _projectDirectory in copy constructor and serialization - Fix CreateItem(string, string, string) to use includeBeforeWildcardExpansionEscaped related: #3121 fixes #13140
JanProvaznik
added a commit
to JanProvaznik/msbuild
that referenced
this pull request
Feb 25, 2026
When using MSBuild's multithreaded mode (-mt / /maxcpucount), the %(RecursiveDir) built-in metadata was lost when items crossed process boundaries to the TaskHost. This caused NuGet pack to flatten directory structures (e.g., build\wix\bundle\bundle.wxs became build\bundle.wxs). Root cause: TaskParameterTaskItem only copied custom metadata when wrapping ITaskItem for TaskHost serialization. RecursiveDir is a built-in metadata that cannot be derived from just the item spec - it requires the original wildcard pattern (_includeBeforeWildcardExpansionEscaped). Fix: Explicitly copy RecursiveDir to custom metadata in TaskParameterTaskItem constructor before serialization, so it survives the cross-process boundary. Also includes related fixes to ProjectItemInstance.cs: - Preserve _projectDirectory in copy constructor and serialization - Fix CreateItem(string, string, string) to use includeBeforeWildcardExpansionEscaped related: dotnet#3121 fixes dotnet#13140
JanProvaznik
added a commit
to JanProvaznik/msbuild
that referenced
this pull request
Mar 3, 2026
When using MSBuild's -mt mode (/maxcpucount), tasks without [MSBuildMultiThreadableTask] are routed to out-of-process TaskHost sidecars. Items passed to these tasks are serialized via TaskParameterTaskItem, which calls CloneCustomMetadataEscaped() — copying only custom metadata. RecursiveDir is a built-in metadata that is non-derivable (requires _includeBeforeWildcardExpansionEscaped), so it's lost during this serialization, returning empty string on the TaskHost side. This causes dotnet pack /mt to flatten directory structures in NuGet packages (e.g., build/wix/bundle/bundle.wxs becomes build/bundle.wxs). Fix: Explicitly copy RecursiveDir to custom metadata in the TaskParameterTaskItem constructor before serialization, so it survives the cross-process boundary. Unlike the previous attempt (PR dotnet#13142, reverted in PR dotnet#13245), this fix ONLY touches the TaskHost serialization boundary (TaskParameterTaskItem). It does NOT change GatherTaskItemOutputs or ProjectItemInstance, which was what caused the NuGet/sfxproj double-append regression. Items that lose RecursiveDir at the MSBuild task callback boundary (e.g., sfxproj targets returning items via TargetOutputs) are unaffected — they already have RecursiveDir=empty before reaching TaskParameterTaskItem. Fixes dotnet#13140 Related: dotnet#3121 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When using MSBuild's multithreaded mode (-mt / /maxcpucount), the %(RecursiveDir) built-in metadata was lost when items crossed process boundaries to the TaskHost. This caused NuGet pack to flatten directory structures (e.g., build\wix\bundle\bundle.wxs became build\bundle.wxs).
Root cause: TaskParameterTaskItem only copied custom metadata when wrapping ITaskItem for TaskHost serialization. RecursiveDir is a built-in metadata that cannot be derived from just the item spec - it requires the original wildcard pattern (_includeBeforeWildcardExpansionEscaped).
Fix: Explicitly copy RecursiveDir to custom metadata in TaskParameterTaskItem constructor before serialization, so it survives the cross-process boundary.
Also includes related fixes to ProjectItemInstance.cs:
related: #3121
fixes #13140