Skip full Build for AOT publish, use Compile instead - #54515
Conversation
There was a problem hiding this comment.
Pull request overview
This PR changes Native AOT publish so it compiles the intermediate IL needed by the AOT pipeline without running the full Build target, avoiding unused managed self-contained artifacts in bin output.
Changes:
- Adds an AOT-specific publish build alternative in
Microsoft.NET.Publish.targets. - Routes
PublishAot=truepublishes throughCompile/resource targets instead of fullBuild. - Updates and adds Native AOT publish tests for clean output and project-reference publishing.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets |
Adds _PublishAotBuildAlternative and excludes AOT publish from the normal full-build path. |
test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs |
Updates property-recording hooks for skipped Build and adds Native AOT publish regression tests. |
51fa828 to
d4fd2c4
Compare
d4fd2c4 to
771e843
Compare
dsplaisted
left a comment
There was a problem hiding this comment.
Looks good. There is some risk here but after reviewing it is less than I was initially thinking.
The PR is targeting main, I think it would need to be re-targeted for .NET 11 and CI should be re-run.
It would be nice to scan popular NuGet packages and see if they include any MSBuild logic that would be broken by this. Copilot flagged Microsoft.XmlSerializer.Generator as having logic that would be broken by this, but it seems like it may not work with NativeAOT anyway, regardless of this change.
|
NuGet compatibility scan: I checked the top 100 stable packages returned by NuGet search for build/buildTransitive/buildMultiTargeting hooks. The only package with a direct |
|
/backport to release/11.0.1xx-rc1 |
|
Started backporting to |
|
@marcpopMSFT backporting to |
|
@marcpopMSFT have we checked this doesn't break iOS and Android? |
|
Per request of maui who expects to be impacted by this, we'll old it from rc1 but get it merged for rc2 early so testing can be done. |
When publishing with PublishAot=true, the implicit Build step was producing a full self-contained managed deployment (apphost, DLLs, deps.json, runtimeconfig, runtime pack files) in the output directory. This output is completely unused by the AOT pipeline, which reads from @(IntermediateAssembly) in obj\, not from bin\ output. This change introduces _PublishAotBuildAlternative that runs Compile (plus resource and satellite assembly targets) instead of full Build for AOT publish. This eliminates the confusing managed artifacts from the output directory while preserving all AOT publish functionality. The target list for AOT publish is: - BuildOnlySettings: configures build-time settings - PrepareForBuild: creates output directories - PrepareResources: compiles .resx to .resources files - Compile: produces IL assembly in obj\ (includes ResolveReferences) - CreateSatelliteAssemblies: generates culture-specific resource DLLs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mentation - Add UseAotOptimizedPublish property (default true for PublishAot) as opt-out to restore old Build behavior during AOT publish - Improve test to verify runtime pack files (System.Private.CoreLib.dll, coreclr.dll) are not present in build output directory - Add publish-build-optimization.md documenting the breaking change, all publish modes, opt-out mechanism, and future work Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The AOT-optimized publish path (UseAotOptimizedPublish) runs Compile instead of full Build, which skips PrepareForRun and therefore GenerateStaticWebAssetsManifest. Web SDK projects publishing with PublishAot then failed because Microsoft.NET.Sdk.StaticWebAssets.References.targets expects the build manifest (staticwebassets.build.json) to exist during publish. Add an _AdditionalAotBuildAlternativeTargets extension point to the AOT build-alternative and have the Static Web Assets SDK contribute GenerateStaticWebAssetsManifest to it. This fixes the failing TrimmingOptions_Are_Defaulted_Correctly_On_Aot_Apps and Publish_WithJson tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
TrimmingOptions_Are_Defaulted_Correctly_On_Aot_Apps recorded properties using the default AfterBuild anchor. The AOT-optimized publish path runs Compile instead of full Build, so AfterBuild never runs and PropertyValues.txt was never written. Record before Publish (which always runs), matching the pattern already used by the AOT tests in GivenThatWeWantToPublishAnAotApp.cs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…imMode Recording before Publish captured TrimMode after PrepareForILLink defaulted it to "full", failing the TrimMode == "" assertion. PrepareForPublish runs after Compile but before publish-time trimming, matching the pre-trimming values the original AfterBuild anchor observed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 56297b38-2478-484c-aaa5-f0c06ab91e34
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 56297b38-2478-484c-aaa5-f0c06ab91e34
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 56297b38-2478-484c-aaa5-f0c06ab91e34
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 56297b38-2478-484c-aaa5-f0c06ab91e34
c628112 to
bfcf675
Compare
|
/tactics |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 56297b38-2478-484c-aaa5-f0c06ab91e34
|
/ba-g all the checks are green so ba appears stuck |
When publishing with PublishAot=true, the implicit Build step was producing a full self-contained managed deployment (apphost, DLLs, deps.json, runtimeconfig, runtime pack files) in the output directory. This output is completely unused by the AOT pipeline, which reads from @(IntermediateAssembly) in obj\, not from bin\ output.
This change introduces _PublishAotBuildAlternative that runs Compile (plus resource and satellite assembly targets) instead of full Build for AOT publish. This eliminates the confusing managed artifacts from the output directory while preserving all AOT publish functionality.
The target list for AOT publish is:
Tactics
Summary
When publishing with
PublishAot=true, the full MSBuildBuildtarget was executing as part of the publish pipeline, producing a complete self-contained managed deployment (apphost, DLLs, deps.json, runtimeconfig.json, and runtime pack files) in the output (bin\\) directory. These artifacts are entirely unused by the AOT pipeline, which reads from@(IntermediateAssembly)inobj\\, not frombin\\. This change introduces a new_PublishOptimizedBuildtarget that runs onlyCompile(plus resource and satellite assembly targets) instead of the fullBuildfor AOT publish, eliminating the confusing managed artifacts from the output directory while preserving all AOT publish functionality. A public opt-out propertyUseOptimizedPublishis provided for projects that rely onBuildextensibility points (e.g.,BeforeBuild,AfterBuild,BeforeTargets/AfterTargets="Build") or packages likeMicrosoft.Extensions.ApiDescription.Server. This fix is scoped to AOT-only in .NET 11 to minimize risk; expansion to other publish modes is planned for .NET 12 early previews (tracked in #55911).Customer Impact
Customers performing
dotnet publish /p:PublishAot=truereceived a cluttered output directory containing the full managed self-contained deployment alongside the expectedpublish\\andnative\\subdirectories. This caused confusion when developers accidentally deployed the build-output DLLs/apphost instead of the AOT-compiled binaries. The issue affects all users on .NET 11 using Native AOT publish. There is no workaround other than manually deleting the spurious files. After this fix, the output directory contains only subdirectories (native\\,publish\\), with no loose managed artifacts. Projects with customBuildtargets or packages withBeforeTargets="Build"hooks must opt out via<UseOptimizedPublish>false</UseOptimizedPublish>.Regression?
Unknown — this is not a regression but rather a long-standing behavior of the publish pipeline where
Buildwas always invoked unconditionally. The PR improves the behavior for .NET 11 Native AOT publish. The feature is new in .NET 11 and targetsrelease/11.0.1xx.Testing
test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs, including assertions that the parent output directory contains no loose files after AOT publish, and a focused test that records the evaluatedCoreBuildDependsOntarget sequence so any unexpected additions require explicit review. 5 lines added intest/Microsoft.NET.Sdk.Web.Tests/PublishTests.cs.Microsoft.Extensions.ApiDescription.Serverwas found with a directBuildhook, documented in the opt-out guidance.$(_AdditionalOptimizedBuildTargets)andUseOptimizedPublish=falseopt-out are provided for workloads.release/11.0.1xxbranch.Risk
Medium. The change alters the publish pipeline for all
PublishAot=trueprojects by skipping the fullBuildtarget in favor ofCompile-only. WhileBuildextensibility points (BeforeBuild,AfterBuild, customBeforeTargets/AfterTargets="Build") are bypassed by default, an opt-out (UseOptimizedPublish=false) is provided and documented. The MAUI/iOS/Android workloads have not yet completed testing, which is why the PR is targeted for rc2. The NuGet ecosystem scan found only one affected package. The change is limited toPublishAot=truescenarios, reducing blast radius.