Skip to content

Enlighten ResolveNonMSBuildProjectOutput task for multithreaded mode #13612

@jankratochvilcz

Description

@jankratochvilcz

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

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions