Migrate GenAPI and GenFacades tasks to the multithreaded task model - #17539
ViktorHofer wants to merge 2 commits into
Conversation
Annotates ClearAssemblyReferenceVersions with [MSBuildMultiThreadableTask] and routes path handling in both assemblies through the injected TaskEnvironment. AbsolutePath is threaded through SourceGenerator and TypeParser, which is what resolves the transitive MSBuildTask0005 chain out of GenPartialFacadeSource. GenAPITask, GenPartialFacadeSource and NotSupportedAssemblyGenerator keep IMultiThreadableTask without the attribute, so they still route to the TaskHost while their paths resolve against the project. Each records why at its declaration: GenAPITask hands raw paths to HostEnvironment, which expands and probes them with process-wide APIs, and the two RoslynBuildTask subclasses subscribe an instance handler to the process-wide AssemblyLoadContext.Resolving event. Two issues found while auditing the migration itself: - GenPartialFacadeSourceGenerator deduplicated seeds with Path.GetFullPath, which both anchors and canonicalizes. GetAbsolutePath only anchors, so Distinct no longer collapsed two spellings of the same seed and the duplicate-name check below it would report them as multiple versions of one assembly. Restored via the GetCanonicalForm polyfill. - Empty compile file item specs were skipped by TypeParser.GetSourceTrees rather than treated as an error. Absolutizing every item spec turned that into a throw, so they are filtered before resolution.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The public API change is breaking, and contract-project-relative paths may resolve against the wrong project directory.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Lite
Findings: 1
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
src/Microsoft.DotNet.GenFacades/GenPartialFacadeSourceGenerator.cs — Preserve the public generator API |
What changed in this PR
Migrates GenAPI and GenFacades tasks to TaskEnvironment path handling and multithreaded task support.
Changes:
- Threads
AbsolutePaththrough facade generation. - Restores seed canonicalization and filters empty source items.
- Adds task environment injection and annotates
ClearAssemblyReferenceVersions. - Review identified a breaking public API change and a contract-project path resolution issue.
| File | Summary |
|---|---|
src/Microsoft.DotNet.GenFacades/TypeParser.cs |
Parses absolute source paths. |
src/Microsoft.DotNet.GenFacades/SourceGenerator.cs |
Uses absolute compile and output paths. |
src/Microsoft.DotNet.GenFacades/NotSupportedAssemblyGenerator.cs |
Resolves task paths through TaskEnvironment. |
src/Microsoft.DotNet.GenFacades/Microsoft.DotNet.GenFacades.csproj |
Includes the path canonicalization helper. |
src/Microsoft.DotNet.GenFacades/GenPartialFacadeSourceGenerator.cs |
Canonicalizes and deduplicates seeds. |
src/Microsoft.DotNet.GenFacades/GenPartialFacadeSource.cs |
Resolves inputs and outputs and filters empty items. |
src/Microsoft.DotNet.GenFacades/ClearAssemblyReferenceVersions.cs |
Enables multithreaded routing and path resolution. |
src/Microsoft.DotNet.GenAPI/GenAPITask.cs |
Adds environment-aware path handling. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
akoeplinger
left a comment
There was a problem hiding this comment.
LGTM but I'd probably change the PR title since the tasks aren't actually migrated
|
@akoeplinger sorry, this wasn't yet ready for review. I just drafted the PR. |
The type is only reachable from GenPartialFacadeSource in the same assembly, and build task packages ship their assembly under tools/ with IncludeBuildOutput=false, so a PackageReference exposes no compile-time assets to bind against. Marking it internal matches SourceGenerator and TypeParser and keeps the AbsolutePath signature change off the public surface.
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Address the two unresolved moderate issues involving whitespace-only paths and empty item specifications.
Review tier: Lite
Findings: None
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
src/Microsoft.DotNet.GenFacades/GenPartialFacadeSourceGenerator.cs — Preserve the public generator API View resolved comment |
Previously missed findings (1)
In code that hasn't changed since last review
src/Microsoft.DotNet.GenFacades/NotSupportedAssemblyGenerator.cs:78
- This resolves every item spec before checking it. An empty
ITaskItem.ItemSpecis still possible even with[Required]on the array; previouslyFile.Exists("")returned false and the task logged the existing not-found error, whereasGetAbsolutePath("")throws and turns the item into an unhandled task failure. Guard empty specs before this call (preserving the prior error/skip behavior).

Seventh safe subset of #17381:
Microsoft.DotNet.GenAPIandMicrosoft.DotNet.GenFacades.One task is annotated with
[MSBuildMultiThreadableTask](ClearAssemblyReferenceVersions); the other three keepIMultiThreadableTaskwithout the attribute, so they continue to route through the TaskHost while their paths still resolve against the project. Routing keys off the attribute alone, so the interface only causesTaskEnvironmentto be injected — removing it would revert path resolution to the process current directory while leaving the task exactly as unsafe.GenAPITaskhands rawLibPath/Assemblyvalues toHostEnvironment, which expands them withEnvironment.ExpandEnvironmentVariablesand probes withDirectory.Exists/File.Exists(Microsoft.Cci.Extensions/HostEnvironment.cs:719-740).GenPartialFacadeSourceandNotSupportedAssemblyGeneratorderive fromRoslynBuildTask, which subscribes an instance handler to the process-wideAssemblyLoadContext.Resolvingevent, so with differingRoslynAssembliesPathvalues one instance can service another's resolution.AbsolutePathis threaded throughSourceGenerator,TypeParserandGenPartialFacadeSourceGenerator. Retyping those helper parameters is what actually resolves the transitiveMSBuildTask0005chain out ofGenPartialFacadeSource. The first two were alreadyinternal;GenPartialFacadeSourceGeneratoris nowinternaltoo, so none of this is public API. It was never reachable as API anyway — build task projects setIncludeBuildOutput=falseand pack undertools/, so aPackageReferenceexposes no compile-time assets, and the only caller in thedotnetorg is the one in-repo call site.Two issues found auditing the migration itself
GenPartialFacadeSourceGeneratordeduplicated seeds withPath.GetFullPath, which anchors and canonicalizes — the comment on the line says "Normalizing and Removing Relative Segments".GetAbsolutePathonly anchors, soDistinct()no longer collapsed two spellings of the same seed, and the duplicate-name check immediately below would have reported them as "multiple versions of these assemblies". Restored with theGetCanonicalForm()polyfill, which required adding the usual<Compile>link to the GenFacades project.AbsolutePathis areadonly structimplementingIEquatable<AbsolutePath>with a path-aware comparer, soDistinct()is sound on it.TypeParser.GetSourceTreesexplicitly skipped empty source files rather than treating them as an error.[Required]on anITaskItem[]only validates the collection, not eachItemSpec, andGetAbsolutePaththrows on an empty path — so absolutizing every item spec turned a skipped entry into a hard failure. They are now filtered before resolution. (ReferenceAssemblyandOutputSourcePathare[Required]scalars, which MSBuild rejects when empty, so they need no guard.)Validation
dotnet build Arcade.slnx -c Release --no-incremental: 0 errors, 0 warnings. Incremental builds skip up-to-date projects, so the analyzer does not re-run and reports a false "0 warnings";--no-incrementalis required for an authoritative count.ClearAssemblyReferenceVersionscall site toFile.Open(Assembly, ...)producesMSBuildTask0003, confirming the analyzer is live on the newly annotated task and that the clean run is meaningful.