Enlighten ResolveNonMSBuildProjectOutput task for multithreaded mode
- Dominant language
- C#
- Stars
- 5.5k
- Forks
- 1.5k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 133
Description
# Enlighten ResolveNonMSBuildProjectOutput task for multithreaded mode
Parent: #11834
## Context
`ResolveNonMSBuildProjectOutput` is the **second-highest-execution unmigrated task** (19.8B total executions) listed in the migration epic (#11834) under "Other (either simple or with unknown issues)". It resolves output paths for non-MSBuild project references by matching against a pre-resolved project output XML blob provided by the IDE.
The task has **no cwd-dependent operations**:
- No `Environment.CurrentDirectory`, `Path.GetFullPath`, or direct `File.*`/`Directory.*` usage
- The only file I/O is `AssemblyName.GetAssemblyName(resolvedPath.ItemSpec)` — this operates on **pre-resolved absolute paths** extracted from the `PreresolvedProjectOutputs` XML string. The paths come from the IDE/solution configuration, not from relative path resolution
- Base class `ResolveProjectBase` uses `reference.GetMetadata("FullPath")` — engine-resolved absolute paths
- All mutable state is instance-scoped (solution configuration cache, project reference lists)
This is an **attribute-only** migration: the task needs `[MSBuildMultiThreadableTask]` but does NOT need `IMultiThreadableTask` or `TaskEnvironment` because its file access uses pre-resolved absolute paths and it performs no relative path resolution, environment variable access, or process launches.
## Approach
Apply the **PR #13045 (attribute-only) pattern**: add `[MSBuildMultiThreadableTask]` to the class declaration. No `IMultiThreadableTask` implementation is needed.
1. Decorate `ResolveNonMSBuildProjectOutput` with `[MSBuildMultiThreadableTask]`.
2. No other code changes required — the task is already thread-safe by design.
## ChangeWave consideration
No behavioral change — the attribute simply declares the task safe for multithreaded scheduling. Observable behavior (return value, `[Output]` properties, error/warning messages) is byte-identical. No ChangeWave needed.
## Test coverage assessment
### Existing unit tests
Tests exist for project reference resolution, pre-resolved output matching, managed assembly detection, and unresolved reference handling.
### Gaps to fill in this PR
- **G1 — Concurrency test.** Two `ResolveNonMSBuildProjectOutput` instances with different `PreresolvedProjectOutputs` XML and `ProjectReferences` execute concurrently, asserting each produces correct, independent `ResolvedOutputPaths` and `UnresolvedProjectReferences` output. Verifies no shared mutable state interference.
- **G2 — Baseline parity.** Verify all existing tests continue to pass with no behavioral changes. `[Output] ResolvedOutputPaths` and `[Output] UnresolvedProjectReferences` items have identical ItemSpec and metadata.
## Acceptance criteria
- [ ] `ResolveNonMSBuildProjectOutput` decorated with `[MSBuildMultiThreadableTask]`.
- [ ] No `IMultiThreadableTask` implementation (not needed — no cwd-dependent operations).
- [ ] All existing unit tests pass on `net472` and `net10.0`.
- [ ] `[Output] ResolvedOutputPaths` and `[Output] UnresolvedProjectReferences` items have byte-identical `ItemSpec` and metadata.
- [ ] Concurrency test (**G1**) added and passes.
- [ ] No new compiler warnings (warnings-as-errors in official build).
- [ ] `multithreaded-task-migration` SKILL sign-off checklist walked and passes.
## Implementation order
1. Apply `[MSBuildMultiThreadableTask]` to `ResolveNonMSBuildProjectOutput`.
2. Add **G1** (concurrency) test.
3. Run `Tasks.UnitTests`; verify clean.
4. Run repo build with `-v quiet` to ensure no new warnings.
## Risks / open questions
- `AssemblyName.GetAssemblyName()` performs actual disk I/O for assembly probing. However, it operates on pre-resolved absolute paths from the IDE blob — no relative path resolution is involved. Under MT execution, concurrent calls to `GetAssemblyName` on different files are safe (no shared state in the static method).
- `GetAssemblyName` is injected via a delegate property for testability. The default delegate is `AssemblyName.GetAssemblyName` — a static, stateless method.
- Base class `ResolveProjectBase` is shared with `AssignProjectConfiguration`. The `[MSBuildMultiThreadableTask]` attribute has `Inherited = false`, so each concrete class must be independently decorated.
## References
- Parent epic: #11834
- Pattern PRs:
- #13045 — Enlighten more tasks that require no change (attribute-only baseline)
- #13267 — Enlighten public versions of intrinsic tasks
- #13069 — Enlighten FindUnderPath and AssignTargetPath
- SKILL: `.github/skills/multithreaded-task-migration/SKILL.md`
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by locating the ResolveNonMSBuildProjectOutput implementation and its existing unit tests, then read .github/skills/multithreaded-task-migration/SKILL.md and run Tasks.UnitTests. Add the attribute-only migration and a concurrency test using separate XML and project references; done means both target frameworks pass with unchanged outputs and no warnings.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- build-system
- Issue type
- Refactor
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100