Add analyzer for TaskEnvironment constructor injection#14401
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 175855cd-9522-42fd-ad8a-928f21c9e4f5
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 175855cd-9522-42fd-ad8a-928f21c9e4f5
There was a problem hiding this comment.
Pull request overview
This pull request adds a new Roslyn analyzer rule to the MSBuild task-authoring analyzer package to encourage constructor-based TaskEnvironment injection for concrete IMultiThreadableTask implementations, aligning author guidance with the engine’s newer constructor-injection capability.
Changes:
- Introduces
MSBuildTask0011(Info) to flag concreteIMultiThreadableTaskclasses missing a public single-TaskEnvironmentconstructor. - Adds analyzer unit tests covering valid/invalid/abstract/inherited-constructor scenarios.
- Updates analyzer documentation and the thread-safe tasks specification to document the recommended pattern and the new rule.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/TaskAnalyzer/TaskEnvironmentConstructorInjectionAnalyzer.cs | Implements the new MSBuildTask0011 analyzer logic. |
| src/TaskAnalyzer/README.md | Documents the new rule, rationale, and scope/severity guidance. |
| src/TaskAnalyzer/DiagnosticIds.cs | Adds the MSBuildTask0011 diagnostic ID constant. |
| src/TaskAnalyzer/DiagnosticDescriptors.cs | Adds the MSBuildTask0011 descriptor and registers it in the descriptor set. |
| src/TaskAnalyzer/AnalyzerReleases.Unshipped.md | Records the new analyzer rule in the unshipped release notes. |
| src/TaskAnalyzer.Tests/TaskEnvironmentConstructorInjectionAnalyzerTests.cs | Adds unit tests validating analyzer behavior across key cases. |
| documentation/specs/multithreading/thread-safe-tasks.md | Updates the spec to mention the analyzer recommendation and constructor-injection guidance. |
There was a problem hiding this comment.
Review Summary — MSBuildTask0011 TaskEnvironmentConstructorInjectionAnalyzer
Overall: Looks good. Clean, well-scoped analyzer with solid test coverage.
Findings
Minor (non-blocking):
-
SupportedDiagnostics allocation (Performance): The
=>expression body allocates on every call. Use{ get; } =pattern likePreferTypedParameterAnalyzer. Pre-existing inconsistency withTransitiveCallChainAnalyzer. -
Potential false positive: If a concrete
IMultiThreadableTaskdoes NOT have aTaskEnvironmentproperty at all (e.g. a task that delegates environment usage to another object), the diagnostic still fires. This seems acceptable for an Info-severity suggestion but could be confusing. Consider adding a test for this scenario. -
No code fix: Unlike MSBuildTask0008, there is no code fix provider. Fine for initial PR but worth noting for follow-up.
Correctness ✅
- Analyzer logic is correct: checks concrete, non-abstract classes implementing IMultiThreadableTask, looks for public single-TaskEnvironment-parameter constructors on the declaring type only.
- The inherited-constructor test correctly validates that base class constructors do not satisfy the check (the engine needs a public ctor on the concrete type).
- Well-known type resolution with early-return is correct.
- Thread safety:
EnableConcurrentExecution()is called, no shared mutable state.
Test Coverage ✅
- Positive case, negative cases (abstract, regular task, has ctor), edge cases (private ctor, multi-param ctor, inherited ctor, inherited interface).
- Missing: task without TaskEnvironment property at all (minor).
Documentation ✅
- README, spec, and AnalyzerReleases.Unshipped.md all updated consistently.
Breaking Changes: None ✅
- Info severity does not trigger WarnAsError. No ChangeWave needed for new analyzer diagnostics.
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
awmgmcpg
To allow these domains, add them to the
network.allowedlist in your workflow frontmatter:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
Generated by Expert Code Review (on open) for #14401 · 64.1 AIC · ⌖ 5.16 AIC · ⊞ 4.8K
baronfel
left a comment
There was a problem hiding this comment.
Looks straightforward to me! I do agree with Copilot about caching the SupportedDiagnostics member though.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 175855cd-9522-42fd-ad8a-928f21c9e4f5
|
@jankratochvilcz I think the one failing test is a flaky test not related to this change. Re-approved! |
Quarantines 2 intermittently failing tests detected across multiple independent CI sources. --- ## `NuGetStaticGraphRestore_InheritsCoordinatorGrant_DoesNotDeadlock` **Flaky-test key** (automated de-duplication — do not edit): ```text flaky-test-id: Microsoft.Build.Coordinator.UnitTests.CoordinatorIntegration_Tests.NuGetStaticGraphRestore_InheritsCoordinatorGrant_DoesNotDeadlock ``` **Action:** Quarantine (6a) Tracked by #14488 - **Distinct sources:** 5 (2 PRs + 3 rolling builds) - **PRs:** #14371, #14401 - **Rolling builds:** [1511941](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1511941), [1517682](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1517682), [1519967](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1519967) - **Legs/TFMs:** CoreOnMac, FullOnWindows, FullOnWindows Release / net10.0, net472 - **Assembly:** MSBuild.Coordinator.UnitTests - **Window:** 2026-07-15 – 2026-07-21 - **Sample error:** `System.TimeoutException : Test failed due to timeout: process is active for more than 60 sec.` --- ## `TaskExceptionHandlingTest` **Flaky-test key** (automated de-duplication — do not edit): ```text flaky-test-id: Microsoft.Build.UnitTests.BackEnd.TaskExecutionHost_Tests.TaskExceptionHandlingTest ``` **Action:** Quarantine (6a) Tracked by #14459 - **Distinct sources:** 5 (1 PR + 4 rolling builds) - **PRs:** #14315 - **Rolling builds:** [1510684](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1510684), [1517216](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1517216), [1519472](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1519472), [1519928](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1519928) - **Legs/TFMs:** FullOnWindows Release / net472 - **Assembly:** Microsoft.Build.Engine.UnitTests - **Window:** 2026-07-15 – 2026-07-21 - **Sample error:** `Shouldly.ShouldAssertException` in `TaskExceptionHandlingTest` > [!WARNING] > <details> > <summary>Firewall blocked 2 domains</summary> > > The following domains were blocked by the firewall during workflow execution: > > - `awmgmcpg` > - `southcentralus0.in.applicationinsights.azure.com` >> To allow these domains, add them to the `network.allowed` list in your workflow frontmatter: > > ```yaml > network: > allowed: > - defaults > - "awmgmcpg" > - "southcentralus0.in.applicationinsights.azure.com" > ``` > > See [Network Configuration](https://github.github.com/gh-aw/reference/network/) for more information. > > </details> > Generated by [Flaky Test Triage](https://github.com/dotnet/msbuild/actions/runs/30006270630) · 126 AIC · ⌖ 6.2 AIC · ⊞ 14.8K · [◷](https://github.com/search?q=repo%3Adotnet%2Fmsbuild+%22gh-aw-workflow-id%3A+flaky-test-detector.agent%22&type=pullrequests) <!-- gh-aw-agentic-workflow: Flaky Test Triage, engine: copilot, version: 1.0.70, model: claude-sonnet-4.6, id: 30006270630, workflow_id: flaky-test-detector.agent, run: https://github.com/dotnet/msbuild/actions/runs/30006270630 --> <!-- gh-aw-workflow-id: flaky-test-detector.agent --> <!-- gh-aw-workflow-call-id: dotnet/msbuild/flaky-test-detector.agent --> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
MSBuildTask0011at Info severity for concreteIMultiThreadableTaskimplementations that do not expose a public single-TaskEnvironmentconstructorReplaces #14400, which GitHub automatically closed when its stacked base branch was deleted after #14315 merged. The branch is now rebased directly onto
main. The rule moved from the originally proposed ID 0010 to 0011 becausemainassignedMSBuildTask0010in #13974.Compatibility
The task-authoring analyzer is currently non-shipping/opt-in, and this diagnostic defaults to Info severity, so it does not break builds using warnings-as-errors. No ChangeWave is needed.
Performance
Measured with the BenchmarkDotNet harness from #14387 in a disposable integration checkout, using the default job on an Apple M4 Max with .NET 10.0.8 / SDK 10.0.300.
MSBuildTask0009is the closest existing symbol-action analyzer.The initial no-reference path measured 48.08 us / 32.41 KB. Short-circuiting after the mandatory
IMultiThreadableTasklookup reduced that to 35.52 us / 30.56 KB, close toMSBuildTask0009at 31.46 us / 30.56 KB. Diagnostic time, allocations, and 1/10/100 scaling are comparable, so no further complexity is justified.Validation
./build.sh -v quietdotnet test --project src/TaskAnalyzer.Tests/TaskAnalyzer.Tests.csproj -c ReleaseFixes #14378