Skip to content

Migrate CreateItem task to multithreaded execution model#13590

Open
jankratochvilcz wants to merge 1 commit intomainfrom
jankratochvilcz/multithreaded/create-item-migration
Open

Migrate CreateItem task to multithreaded execution model#13590
jankratochvilcz wants to merge 1 commit intomainfrom
jankratochvilcz/multithreaded/create-item-migration

Conversation

@jankratochvilcz
Copy link
Copy Markdown
Contributor

Migrates the CreateItem task to MSBuild's multithreaded execution model, applying the "absolutize-at-boundary" pattern previously established for other tasks (see #13267).

Fixes #13568.

Changes

src/Tasks/CreateItem.cs

  • Added [MSBuildMultiThreadableTask] attribute and IMultiThreadableTask interface
  • Added TaskEnvironment property with = TaskEnvironment.Fallback default (matching all other migrated tasks)
  • Changed FileMatcher.Default.GetFiles(null, ...)FileMatcher.Default.GetFiles(TaskEnvironment.ProjectDirectory, ...) so relative wildcard patterns resolve against the project directory instead of process cwd

src/Tasks.UnitTests/CreateItem_Tests.cs — 7 new tests

  • WildcardInclude_ResolvesAgainstProjectDirectory — relative wildcard uses ProjectDirectory, not cwd
  • WildcardExclude_ResolvesAgainstProjectDirectory — Exclude wildcards also resolve against ProjectDirectory
  • ConcurrentProjectDirectories_ReturnDisjointFileSets — two tasks with different ProjectDirectory values return correct disjoint results
  • RecursiveDir_WithCustomProjectDirectory — RecursiveDir metadata correct under custom ProjectDirectory
  • AbsoluteWildcard_IgnoresProjectDirectory — absolute patterns unaffected by ProjectDirectory
  • FallbackTaskEnvironment_WildcardParityWithLegacy — Fallback default matches legacy cwd behavior
  • LiteralItems_UnaffectedByProjectDirectory — non-wildcard items unchanged

ChangeWave consideration

No ChangeWave required. For absolute and rooted wildcard patterns, GetFiles ignores the project directory entirely. For relative wildcard patterns, the base directory changes from Environment.CurrentDirectory to TaskEnvironment.ProjectDirectory. In legacy (non-MT) execution the two are equal during normal builds, so observable behavior is unchanged.

Validation

  • All existing CreateItem_Tests pass on net10.0 with no behavior changes
  • All 7 new tests pass on net10.0
  • ./build.sh -v quiet succeeds with 0 warnings

Apply the absolutize-at-boundary pattern to CreateItem: mark it with
[MSBuildMultiThreadableTask], implement IMultiThreadableTask with
TaskEnvironment defaulted to Fallback, and route wildcard expansion
through TaskEnvironment.ProjectDirectory instead of null (cwd).

This ensures CreateItem resolves relative wildcard patterns against the
correct project directory under multithreaded execution, where multiple
projects may share a single process cwd.

Fixes #13568

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 21, 2026 16:06
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 migrates the built-in CreateItem task to MSBuild’s multithreaded execution model by wiring it into IMultiThreadableTask and ensuring wildcard expansion is rooted at the task’s project directory instead of process-wide current directory.

Changes:

  • Marked CreateItem as [MSBuildMultiThreadableTask] and implemented IMultiThreadableTask with a TaskEnvironment defaulting to TaskEnvironment.Fallback.
  • Updated wildcard expansion to call FileMatcher.GetFiles(TaskEnvironment.ProjectDirectory, ...) instead of using process cwd.
  • Added new unit tests validating project-directory-based wildcard behavior (Include/Exclude/RecursiveDir/absolute patterns/literals).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/Tasks/CreateItem.cs Adds MT task plumbing and switches wildcard expansion base directory to TaskEnvironment.ProjectDirectory.
src/Tasks.UnitTests/CreateItem_Tests.cs Adds tests for ProjectDirectory-rooted wildcard expansion scenarios.

Comment on lines 6 to 8
using System.IO;
using System.Linq;
using Microsoft.Build.Execution;
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

using System.Linq; appears unused in this test file; please remove it to avoid unnecessary imports and potential analyzer noise.

Copilot uses AI. Check for mistakes.
Comment on lines +449 to +456
/// <summary>
/// Relative wildcard Include resolves against TaskEnvironment.ProjectDirectory, not cwd.
/// </summary>
[Fact]
public void WildcardInclude_ResolvesAgainstProjectDirectory()
{
using TestEnvironment env = TestEnvironment.Create(_testOutput);

Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

PR description says 7 new tests were added (including concurrency + fallback-parity cases), but this file currently adds 5 tests. Either add the missing tests or update the PR description so it matches what’s actually in the diff.

Copilot uses AI. Check for mistakes.
Comment on lines +569 to +570
/// Pre-expanded literal items (no wildcards) produce identical output regardless of
/// ProjectDirectory, Exclude, and AdditionalMetadata settings.
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

The XML doc for this test claims the output is identical “regardless of ProjectDirectory”, but the test never sets TaskEnvironment (so it doesn’t actually vary ProjectDirectory). Consider either setting a non-default TaskEnvironment here or rewording the comment to reflect what’s being asserted.

Suggested change
/// Pre-expanded literal items (no wildcards) produce identical output regardless of
/// ProjectDirectory, Exclude, and AdditionalMetadata settings.
/// Verifies that for pre-expanded literal items (no wildcards), <see cref="CreateItem"/>
/// applies <c>Exclude</c> filtering and <c>AdditionalMetadata</c> to the resulting items.

Copilot uses AI. Check for mistakes.
Comment thread src/Tasks/CreateItem.cs
Comment on lines +185 to 186
(files, _, _, string? globFailure) = FileMatcher.Default.GetFiles(TaskEnvironment.ProjectDirectory, i.ItemSpec);
if (globFailure != null)
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

FileMatcher.GetFiles base directory change is the key MT migration behavior; it would be good to add a regression test that runs two CreateItem instances with different TaskEnvironment.ProjectDirectory values concurrently (or at least back-to-back with different project dirs) and asserts they return disjoint file sets for the same relative glob, to guard against cross-project leakage in multithreaded builds.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

24-Dimension Expert Review — PR #13590

Test Coverage: Missing Tests from PR Description

The PR description lists 7 new tests, but only 5 appear in the diff. Two tests are missing:

  1. ConcurrentProjectDirectories_ReturnDisjointFileSets — The PR's stated purpose is making CreateItem safe under multithreaded execution. This test (running two tasks with different ProjectDirectory values concurrently and verifying disjoint results) would provide direct evidence of correct isolation.

  2. FallbackTaskEnvironment_WildcardParityWithLegacy — This test would verify that the default TaskEnvironment.Fallback produces identical wildcard results to the previous null-based behavior. This is the key backwards-compatibility assurance for existing builds.

The unused using System.Linq; import (line 7) is likely a leftover from these missing tests.

Recommendation: Either include the two missing tests in this PR (they are the most important coverage for the migration's correctness claims), or update the PR description to remove them and file a follow-up issue.


Minor Naming Observations (NIT)

The existing tests in this file use flat PascalCase (CreateItemInclude, AdditionalMetadataOverwriteExisting) while the new tests use Subject_Behavior style (WildcardInclude_ResolvesAgainstProjectDirectory). Both styles are common in the broader codebase, so this is just a style note — not blocking.

LiteralItems_UnaffectedByProjectDirectory doesn't actually set a custom TaskEnvironment with a different ProjectDirectory to prove the claim in its name. It effectively tests Exclude filtering + AdditionalMetadata on literal items. Consider either renaming to better describe what it actually tests, or adding a TaskEnvironment with a custom ProjectDirectory to match the name's claim.

Generated by Expert Code Review (on open) for issue #13590 · ● 15.6M ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

24-Dimension Expert Review Summary

Clean, well-structured migration that follows the established pattern from ~47 previously migrated tasks. The production code change is minimal and correct. No blocking issues found.

# Dimension Verdict
1 Backwards Compatibility ✅ LGTM
2 ChangeWave Discipline ✅ LGTM
3 Performance & Allocation Awareness ✅ LGTM (NIT: cache ProjectDirectory outside loop)
4 Test Coverage & Completeness ⚠️ MODERATE — 2 of 7 claimed tests missing from diff
5 Error Message Quality ✅ LGTM
6 Logging & Diagnostics Rigor ✅ LGTM
7 String Comparison Correctness ✅ LGTM
8 API Surface Discipline ✅ LGTM
9 MSBuild Target Authoring ✅ LGTM (no targets/props changed)
10 Design Before Implementation ✅ LGTM
11 Cross-Platform Correctness ✅ LGTM
12 Code Simplification ✅ LGTM (NIT: unused using System.Linq)
13 Concurrency & Thread Safety ✅ LGTM
14 Naming Precision ✅ LGTM (NIT: naming style)
15 SDK Integration Boundaries ✅ LGTM
16 Idiomatic C# Patterns ✅ LGTM
17 File I/O & Path Handling ✅ LGTM
18 Documentation Accuracy ✅ LGTM
19 Build Infrastructure Care ✅ LGTM
20 Scope & PR Discipline ⚠️ MODERATE — PR description claims 7 tests, diff has 5
21 Evaluation Model Integrity ✅ LGTM
22 Correctness & Edge Cases ✅ LGTM
23 Dependency Management ✅ LGTM
24 Security Awareness ✅ LGTM

Key findings:

  • Production code is correct and follows the established pattern. The nullTaskEnvironment.ProjectDirectory change is semantically equivalent in Fallback mode and correctly enables isolated project directories in MT mode.
  • Two tests from the PR description are missing. ConcurrentProjectDirectories_ReturnDisjointFileSets and FallbackTaskEnvironment_WildcardParityWithLegacy are not in the diff. These would provide the strongest validation of this migration's correctness claims.
  • Minor NITs: unused using System.Linq, cacheable ProjectDirectory access in loop, naming style.

No BLOCKING issues. Overall this is a clean, well-executed migration PR.

Generated by Expert Code Review (on open) for issue #13590 · ● 15.6M

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[NIT] Code Simplification

using System.Linq is added but none of the 5 new test methods (nor any existing test in this file) use any System.Linq extension method. This appears to be a leftover from the two tests mentioned in the PR description (ConcurrentProjectDirectories_ReturnDisjointFileSets and FallbackTaskEnvironment_WildcardParityWithLegacy) that are not present in the diff.

Recommendation: Remove the unused using System.Linq; directive, or add the two missing tests that likely depend on it.

Comment thread src/Tasks/CreateItem.cs
else if (isLegalFileSpec)
{
(files, _, _, string? globFailure) = FileMatcher.Default.GetFiles(null /* use current directory */, i.ItemSpec);
(files, _, _, string? globFailure) = FileMatcher.Default.GetFiles(TaskEnvironment.ProjectDirectory, i.ItemSpec);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[NIT] Performance & Allocation Awareness

TaskEnvironment.ProjectDirectory is accessed inside the foreach loop (via TryExpandWildcards). In Fallback mode, each access invokes NativeMethods.GetCurrentDirectory() (a syscall + string allocation) and wraps the result in an AbsolutePath struct. While the old code with null also involved per-item file system resolution internally, caching the value before the loop is a minor cleanliness improvement:

string projectDir = TaskEnvironment.ProjectDirectory;
// then inside loop:
(files, _, _, string? globFailure) = FileMatcher.Default.GetFiles(projectDir, i.ItemSpec);

Note: The practical impact is small since CreateItem tasks typically have few wildcard items, so this is NIT-level rather than a real performance concern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enlighten CreateItem task for multithreaded mode

2 participants