-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Skip full Build for AOT publish, use Compile instead #54515
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
marcpopMSFT
wants to merge
2
commits into
main
Choose a base branch
from
marcpopMSFT-noimplicitbuildonpublish
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| # Publish Build Optimization | ||
|
|
||
| ## Overview | ||
|
|
||
| When `dotnet publish` runs, it implicitly runs a full `Build` before the publish step. | ||
| This is necessary because the SDK cannot assume the previous `dotnet build` used the same | ||
| configuration (e.g., the default build configuration is `Debug` while publish defaults to | ||
| `Release`), runtime identifier, or other settings. | ||
|
|
||
| However, for some publish modes, the full `Build` output (written to `bin\<config>\<tfm>\<rid>\`) | ||
| is never used by the publish pipeline. The publish steps read from intermediate outputs | ||
| (`obj\`) and resolution items, not from the `bin\` directory. This means the `Build` step | ||
| produces artifacts that are confusing to users and automation, as `bin\<config>\<tfm>\<rid>\` | ||
| contains a full self-contained managed deployment that is not the intended output. | ||
|
|
||
| ## How Publish Modes Work Today | ||
|
|
||
| ### Common Architecture | ||
|
|
||
| All publish modes share this flow: | ||
|
|
||
| 1. **Build** (or equivalent) — produces the IL assembly at `@(IntermediateAssembly)` in `obj\` | ||
| 2. **ComputeResolvedFilesToPublishList** — collects files to publish from `@(IntermediateAssembly)`, | ||
| `@(RuntimeCopyLocalItems)`, `@(RuntimePackAsset)`, and content items | ||
| 3. **Post-processing** — mode-specific transformations (ILC, ILLink, crossgen2, bundler) | ||
| 4. **Copy to PublishDir** — final output written to `bin\<config>\<tfm>\<rid>\publish\` | ||
|
|
||
| Key insight: All post-processing steps read from `@(IntermediateAssembly)` (obj) and | ||
| `@(ResolvedFileToPublish)` (resolved from NuGet/project references), **never** from the | ||
| `Build` output directory. | ||
|
|
||
| ### PublishAot (Native AOT) | ||
|
|
||
| **Status: Optimized (this PR)** | ||
|
|
||
| - `IlcCompile` reads `@(IntermediateAssembly)` from `obj\` and produces a native binary | ||
| - The full `Build` was running a self-contained deployment to `bin\<config>\<tfm>\<rid>\`, | ||
| including apphost, managed DLLs, deps.json, runtimeconfig.json, and runtime pack files | ||
| - **None of these files are used** by the AOT pipeline | ||
| - **Optimization**: Replace `Build` with `Compile` (plus resource/satellite targets) | ||
| - **Opt-out**: Set `UseAotOptimizedPublish=false` to restore full Build behavior | ||
|
|
||
| Target chain for optimized AOT publish: | ||
| ``` | ||
| BuildOnlySettings → PrepareForBuild → PrepareResources → Compile → CreateSatelliteAssemblies | ||
| ``` | ||
|
|
||
| Where `Compile` includes `ResolveReferences → ResolveProjectReferences → CoreCompile`. | ||
|
|
||
| ### PublishTrimmed (IL Trimming) | ||
|
|
||
| **Status: Not yet optimized — candidate for future optimization** | ||
|
|
||
| - `ILLink` (the IL trimmer) processes `@(ResolvedFileToPublish)` items marked with | ||
| `PostprocessAssembly=true` | ||
| - These items come from `ComputeResolvedFilesToPublishList` which reads from | ||
| `@(IntermediateAssembly)` (obj) and resolved references | ||
| - The `Build` output in `bin\` is not consumed by the trimmer | ||
| - The same `Compile`-based optimization would apply here | ||
|
|
||
| ### PublishReadyToRun (R2R / Crossgen2) | ||
|
|
||
| **Status: Not yet optimized — candidate for future optimization** | ||
|
|
||
| - `RunCrossgen2` processes `@(ResolvedFileToPublish)` items | ||
| - Input assemblies come from resolution, not from `Build` output | ||
| - Same optimization opportunity as trimming | ||
|
|
||
| ### PublishSingleFile (Single-File Bundling) | ||
|
|
||
| **Status: Not yet optimized — candidate for future optimization** | ||
|
|
||
| - `GenerateSingleFileBundle` bundles `@(ResolvedFileToPublish)` items into one executable | ||
| - All inputs come from resolved items and `@(IntermediateAssembly)` | ||
| - Same optimization opportunity | ||
|
|
||
| ### Combined Modes | ||
|
|
||
| These modes can be combined (e.g., `PublishAot` implies `PublishTrimmed`). The optimization | ||
| applies when the outermost mode is optimized: | ||
|
|
||
| | Combination | Optimized? | Notes | | ||
| |---|---|---| | ||
| | PublishAot (implies trimmed) | ✅ Yes | AOT is the outermost mode | | ||
| | PublishTrimmed + PublishSingleFile | ❌ Not yet | Future candidate | | ||
| | PublishReadyToRun + PublishSingleFile | ❌ Not yet | Future candidate | | ||
| | PublishTrimmed alone | ❌ Not yet | Future candidate | | ||
| | PublishReadyToRun alone | ❌ Not yet | Future candidate | | ||
|
|
||
| ## Breaking Change: AOT Publish Build Optimization | ||
|
|
||
| ### What Changed | ||
|
|
||
| Starting in .NET 10, `dotnet publish` with `PublishAot=true` no longer runs a full `Build` | ||
| before publish. Instead, it runs only `Compile` (and resource/satellite assembly targets). | ||
|
|
||
| ### Impact | ||
|
|
||
| - **BeforeBuild / AfterBuild targets**: These will not execute during AOT publish. If you have | ||
| custom targets attached to `BeforeBuild`, `AfterBuild`, or using | ||
| `BeforeTargets="Build"` / `AfterTargets="Build"`, they will not run. | ||
| - **Workaround**: Attach your targets to `BeforeTargets="Publish"` / `AfterTargets="Publish"`, | ||
| or to `BeforeTargets="Compile"` / `AfterTargets="Compile"` instead. | ||
| - **PostBuildEvent**: Will not execute during AOT publish (consistent with `--no-build` behavior). | ||
| - **Third-party NuGet Build hooks**: Targets from NuGet packages that hook into `Build` will be | ||
| skipped (consistent with `--no-build` behavior). | ||
| - **Output directory**: `bin\<config>\<tfm>\<rid>\` will no longer contain managed apphost, | ||
| DLLs, deps.json, runtimeconfig.json, or runtime pack files. Only `native\` and `publish\` | ||
| subdirectories will be present. | ||
|
|
||
| ### Opt-Out | ||
|
|
||
| To restore the previous behavior of running a full `Build` before AOT publish, set: | ||
|
|
||
| ```xml | ||
| <PropertyGroup> | ||
| <UseAotOptimizedPublish>false</UseAotOptimizedPublish> | ||
| </PropertyGroup> | ||
| ``` | ||
|
|
||
| Or pass it on the command line: | ||
|
|
||
| ``` | ||
| dotnet publish /p:UseAotOptimizedPublish=false | ||
| ``` | ||
|
|
||
| ## Future Work | ||
|
|
||
| The same optimization could be applied to `PublishTrimmed`, `PublishReadyToRun`, and | ||
| `PublishSingleFile` modes, as they all share the same architecture of reading from | ||
| `@(IntermediateAssembly)` and resolved references rather than `Build` output. Each mode | ||
| would need: | ||
|
|
||
| 1. Its own condition check (e.g., `UseTrimmingOptimizedPublish`) | ||
| 2. The same target chain: `BuildOnlySettings → PrepareForBuild → PrepareResources → Compile → CreateSatelliteAssemblies` | ||
| 3. Tests verifying no managed artifacts in the output directory | ||
| 4. Documentation of the breaking change for that mode |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.