Skip to content

Skip full Build for AOT publish, use Compile instead - #54515

Merged
marcpopMSFT merged 12 commits into
release/11.0.1xxfrom
marcpopMSFT-noimplicitbuildonpublish
Aug 27, 2026
Merged

Skip full Build for AOT publish, use Compile instead#54515
marcpopMSFT merged 12 commits into
release/11.0.1xxfrom
marcpopMSFT-noimplicitbuildonpublish

Conversation

@marcpopMSFT

@marcpopMSFT marcpopMSFT commented May 29, 2026

Copy link
Copy Markdown
Member

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

Tactics

Summary

When publishing with PublishAot=true, the full MSBuild Build target 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) in obj\\, not from bin\\. This change introduces a new _PublishOptimizedBuild target that runs only Compile (plus resource and satellite assembly targets) instead of the full Build for AOT publish, eliminating the confusing managed artifacts from the output directory while preserving all AOT publish functionality. A public opt-out property UseOptimizedPublish is provided for projects that rely on Build extensibility points (e.g., BeforeBuild, AfterBuild, BeforeTargets/AfterTargets="Build") or packages like Microsoft.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=true received a cluttered output directory containing the full managed self-contained deployment alongside the expected publish\\ and native\\ 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 custom Build targets or packages with BeforeTargets="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 Build was always invoked unconditionally. The PR improves the behavior for .NET 11 Native AOT publish. The feature is new in .NET 11 and targets release/11.0.1xx.

Testing

  • Unit/integration tests added: 175 lines of new tests added in 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 evaluated CoreBuildDependsOn target sequence so any unexpected additions require explicit review. 5 lines added in test/Microsoft.NET.Sdk.Web.Tests/PublishTests.cs.
  • Ecosystem compatibility: NuGet top-100 package scan performed; only Microsoft.Extensions.ApiDescription.Server was found with a direct Build hook, documented in the opt-out guidance.
  • MAUI/mobile concern: iOS and Android workload impact was flagged; the PR is intentionally held from rc1 and targeting rc2 to allow MAUI team testing. The extension point $(_AdditionalOptimizedBuildTargets) and UseOptimizedPublish=false opt-out are provided for workloads.
  • CI: PR CI runs on the release/11.0.1xx branch.

Risk

Medium. The change alters the publish pipeline for all PublishAot=true projects by skipping the full Build target in favor of Compile-only. While Build extensibility points (BeforeBuild, AfterBuild, custom BeforeTargets/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 to PublishAot=true scenarios, reducing blast radius.

Copilot AI review requested due to automatic review settings May 29, 2026 20:36
@marcpopMSFT
marcpopMSFT requested review from a team as code owners May 29, 2026 20:36
Comment thread src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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=true publishes through Compile/resource targets instead of full Build.
  • 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.

Comment thread src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets Outdated
Comment thread test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs Outdated
Comment thread src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets Outdated
@marcpopMSFT
marcpopMSFT force-pushed the marcpopMSFT-noimplicitbuildonpublish branch 2 times, most recently from 51fa828 to d4fd2c4 Compare June 8, 2026 20:46
@marcpopMSFT marcpopMSFT added the needs team triage Requires a full team discussion label Jun 8, 2026
@marcpopMSFT
marcpopMSFT requested a review from dsplaisted June 10, 2026 20:40
@marcpopMSFT marcpopMSFT added breaking-change Using this label will notify dotnet/compat and trigger a request to file a compat bug and removed needs team triage Requires a full team discussion labels Jun 10, 2026
@marcpopMSFT
marcpopMSFT force-pushed the marcpopMSFT-noimplicitbuildonpublish branch from d4fd2c4 to 771e843 Compare July 2, 2026 22:31
dsplaisted
dsplaisted previously approved these changes Aug 18, 2026

@dsplaisted dsplaisted left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets Outdated
Comment thread test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs Outdated
Comment thread test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs Outdated
Comment thread documentation/general/publish-build-optimization.md Outdated
Comment thread src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets Outdated
@marcpopMSFT
marcpopMSFT requested a review from a team as a code owner August 18, 2026 18:16
@marcpopMSFT

Copy link
Copy Markdown
Member Author

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 Build hook was Microsoft.Extensions.ApiDescription.Server 10.0.11 (_GenerateOpenApiDocuments, BeforeTargets="Build"). That target also consumes $(TargetPath) from the build output, so projects that need OpenAPI document generation during AOT publish need the documented <UseAotOptimizedPublish>false</UseAotOptimizedPublish> opt-out. No other direct Build hooks were found in the scanned packages.

@marcpopMSFT
marcpopMSFT changed the base branch from main to release/11.0.1xx August 18, 2026 18:16
@marcpopMSFT
marcpopMSFT dismissed dsplaisted’s stale review August 18, 2026 18:16

The base branch was changed.

@marcpopMSFT

Copy link
Copy Markdown
Member Author

/backport to release/11.0.1xx-rc1

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/11.0.1xx-rc1 (link to workflow run)

@github-actions

Copy link
Copy Markdown
Contributor

@marcpopMSFT backporting to release/11.0.1xx-rc1 was not run because the source pull request has not been merged. Please merge this pull request before requesting a backport.

@agocke
agocke requested a review from sbomer August 20, 2026 17:36
@jonathanpeppers

Copy link
Copy Markdown
Member

@marcpopMSFT have we checked this doesn't break iOS and Android?

Comment thread src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets Outdated
@marcpopMSFT

Copy link
Copy Markdown
Member Author

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.

Comment thread src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets Outdated
Comment thread documentation/general/publish-build-optimization.md Outdated
Comment thread documentation/general/publish-build-optimization.md Outdated
marcpopMSFT and others added 11 commits August 25, 2026 16:52
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
@marcpopMSFT
marcpopMSFT force-pushed the marcpopMSFT-noimplicitbuildonpublish branch from c628112 to bfcf675 Compare August 25, 2026 23:53
@marcpopMSFT

Copy link
Copy Markdown
Member Author

/tactics

Comment thread test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 56297b38-2478-484c-aaa5-f0c06ab91e34
@marcpopMSFT
marcpopMSFT enabled auto-merge (squash) August 27, 2026 20:45
@marcpopMSFT

Copy link
Copy Markdown
Member Author

/ba-g all the checks are green so ba appears stuck

@marcpopMSFT
marcpopMSFT merged commit 705d7cc into release/11.0.1xx Aug 27, 2026
24 checks passed
@marcpopMSFT
marcpopMSFT deleted the marcpopMSFT-noimplicitbuildonpublish branch August 27, 2026 22:22
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-rc2 milestone Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change Using this label will notify dotnet/compat and trigger a request to file a compat bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants