Make Microsoft.Build.Tasks.CodeAnalysis AOT and single-file clean#84335
Make Microsoft.Build.Tasks.CodeAnalysis AOT and single-file clean#84335JeremyKuhne wants to merge 1 commit into
Conversation
A .NET copy of this task assembly ships in the .NET SDK's Native AOT CLI, so it must be clean for trimming, AOT, and single-file publishing. Enable IsAotCompatible (which turns on the trim, AOT, and single-file analyzers) and resolve the resulting warnings. - Delete the unused Utilities.TryGetAssemblyPath (it used Assembly.CodeBase/Location). - Guard the framework-to-core bridge csc.rsp/vbc.rsp lookups with #if NETFRAMEWORK; they only run on .NET Framework MSBuild and are dead code on .NET Core. - Scope-suppress the one unavoidable IL3000 in GetBuildTaskDirectory. Assembly.Location is required to locate the sibling compiler when loaded as loose files; single-file hosts fall back to AppContext.BaseDirectory and receive the compiler path out-of-band. [RequiresAssemblyFiles] cannot be used because it would flow onto the non-annotated ToolTask overrides (IL3003). - Annotate FatalError.CopyHandlersTo with [RequiresUnreferencedCode]; this shared Contracts file is compiled into the task and its reflection otherwise produces IL2026/IL2075.
There was a problem hiding this comment.
Pull request overview
This PR updates Microsoft.Build.Tasks.CodeAnalysis to be compatible with trimming, Native AOT, and single-file publishing scenarios by enabling the IsAotCompatible build setting for modern target frameworks and addressing the resulting IL linker/analyzer warnings via conditional compilation and targeted annotations/suppressions.
Changes:
- Enabled
IsAotCompatible(trim/AOT/single-file analyzers) for the .NET TFMs in the MSBuild task project. - Removed an unused helper (
Utilities.TryGetAssemblyPath) that relied onAssembly.CodeBase/Location. - Guarded
csc.rsp/vbc.rsplookup logic behind#if NETFRAMEWORK, added a scoped IL3000 suppression + runtime fallback for single-file/AOT hosts, and annotated reflection-heavy handler-copying withRequiresUnreferencedCode.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/Dependencies/Contracts/ErrorReporting/FatalError.cs | Annotates CopyHandlersTo with RequiresUnreferencedCode (on .NET builds) to satisfy trim analyzer around reflection use. |
| src/Compilers/Core/MSBuildTask/Vbc.cs | Wraps vbc.rsp lookup (via Assembly.Location) in #if NETFRAMEWORK to avoid single-file analyzer warnings on .NET. |
| src/Compilers/Core/MSBuildTask/Utilities.cs | Removes unused TryGetAssemblyPath (and System.Reflection using) to avoid assembly-path APIs. |
| src/Compilers/Core/MSBuildTask/MSBuild/Microsoft.Build.Tasks.CodeAnalysis.csproj | Enables IsAotCompatible for net8.0+ compatible TFMs (e.g., net10.0) while leaving net472 unaffected. |
| src/Compilers/Core/MSBuildTask/ManagedToolTask.cs | Adds a scoped IL3000 suppression and makes GetBuildTaskDirectory robust for single-file/AOT (fallback to AppContext.BaseDirectory). |
| src/Compilers/Core/MSBuildTask/Csc.cs | Wraps csc.rsp lookup (via Assembly.Location) in #if NETFRAMEWORK to avoid single-file analyzer warnings on .NET. |
| clean for trimming, AOT, and single-file publishing. IsAotCompatible enables all three | ||
| analyzers (trim, AOT, and single-file). | ||
| --> | ||
| <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible> |
There was a problem hiding this comment.
IsTargetFrameworkCompatible(net10.0, net8.0) is going to be true, right? But perhaps instead of "hard-coding" net8.0 here, we could check "is .net core" somehow?
There was a problem hiding this comment.
Using IsTargetFrameworkCompatible is the recommended way to do this (there is '$(TargetFrameworkIdentifier)' == '.NETCoreApp' but it doesn't always work due to MSBuild evaluation rules).
Another way is to write '$(TargetFramework)' == '$(NetRoslynSourceBuild)'.
| // | ||
| // The single-file / native AOT case is handled at run time: Assembly.Location returns an empty | ||
| // string and we fall back to AppContext.BaseDirectory below. In those hosts the real compiler | ||
| // location is supplied out-of-band anyway (e.g. via the CscToolPath/VbcToolPath MSBuild property), |
There was a problem hiding this comment.
Can you elaborate what exactly is the single-file / native AOT scenario where the compiler is supplied via CscToolPath only?
| // through these members. | ||
| // This should not happen in supported product scenarios but could happen if a non-supported | ||
| // scenario tried to load our task and call through these members. | ||
| throw new InvalidOperationException("Unable to determine the location of the build task assembly."); |
There was a problem hiding this comment.
Is this reachable now? Can it happen that AppContext.BaseDirectory is ever null or empty?
|
@jjonescz I've flipped this to draft until I fully rationalize the state coming from the muxer (dotnet.exe). I have dotnet/msbuild#14064 pending that I used the changes here to test with the SDK, but I think that I might be depending on the wrong directory info. |
A .NET copy of
Microsoft.Build.Tasks.CodeAnalysisships in the .NET SDK's Native AOT CLI, so the assembly must be clean for trimming, AOT, and single-file publishing.This enables
IsAotCompatibleon the .NET build (which turns on the trim, AOT, and single-file analyzers) and resolves the resulting warnings. All changes are annotations, dead-code guards, or a single scoped suppression - there is no runtime behavior change.Changes
Utilities.TryGetAssemblyPath- deleted. It was unused and relied onAssembly.CodeBase/Location.csc.rsp/vbc.rsplookups (Csc.cs,Vbc.cs) - wrapped in#if NETFRAMEWORK. They only run insideif (IsSdkFrameworkToCoreBridgeTask), which is compile-timefalseon .NET Core, so theAssembly.Locationuse there is dead code on the AOT target framework. Behavior is unchanged for the net472 bridge task.ManagedToolTask.GetBuildTaskDirectory- one scoped[UnconditionalSuppressMessage]for the single unavoidableIL3000. The task needs its own on-disk directory to locate the siblingbincorecompiler when deployed as loose files (the normal MSBuild scenario). Single-file / native AOT hosts fall back toAppContext.BaseDirectoryand receive the compiler path out-of-band (CscToolPath/VbcToolPath).[RequiresAssemblyFiles]cannot be used because it would have to flow onto the sealedToolTaskoverrides (GenerateFullPathToTool,GenerateCommandLineCommands,ToolName,ExecuteTool) whose base declarations are not annotated, which is itself an error (IL3003).FatalError.CopyHandlersTo- annotated[RequiresUnreferencedCode]. This shared Contracts file is globbed into the task assembly, and its reflection over the target type otherwise producesIL2026/IL2075once the trim analyzer is enabled.Validation
Built with
RunAnalyzers=trueand confirmed 0 IL warnings / 0 errors on:net10.0(the AOT target)net472Microsoft.Build.Tasks.CodeAnalysis.Sdkbridge variantMicrosoft Reviewers: Open in CodeFlow