Skip to content

[browser] WebAssembly SDK targets more incremental#125367

Open
maraf wants to merge 13 commits intomainfrom
maraf/WasmSdkIncremental
Open

[browser] WebAssembly SDK targets more incremental#125367
maraf wants to merge 13 commits intomainfrom
maraf/WasmSdkIncremental

Conversation

@maraf
Copy link
Copy Markdown
Member

@maraf maraf commented Mar 10, 2026

Summary

Improves MSBuild incrementalism for WebAssembly browser build targets in Microsoft.NET.Sdk.WebAssembly.Browser.targets. On no-op rebuilds where inputs have not changed, the expensive ConvertDllsToWebcil and GenerateWasmBootJson tasks are now skipped via MSBuild's Inputs/Outputs mechanism.

Changes

Webcil Conversion

Split _ResolveWasmOutputs into 3 targets:

  • _ComputeWasmBuildCandidates (always runs) — resolves build asset candidates, classifies DLLs vs framework pass-throughs, computes expected webcil output paths
  • _ConvertBuildDllsToWebcil (incremental) — runs ConvertDllsToWebcil only when DLL inputs are newer than webcil outputs
  • _ResolveWasmOutputs (always runs) — reconstructs webcil items via MSBuild item transforms, classifies framework candidates, calls DefineStaticWebAssets

Boot JSON Generation

Split _GenerateBuildWasmBootJson into 3 targets:

  • _ResolveBuildWasmBootJsonEndpoints (always runs) — resolves endpoints and fingerprinted assets
  • _WriteBuildWasmBootJsonFile (incremental) — writes boot JSON file only when inputs change
  • _GenerateBuildWasmBootJson (always runs) — defines static web assets from the boot JSON output

Split GeneratePublishWasmBootJson into 2 targets:

  • _ResolvePublishWasmBootJsonInputs (always runs) — resolves publish endpoints
  • GeneratePublishWasmBootJson (incremental) — writes boot JSON only when inputs change

Touch outputs for content-comparison tasks

Both ConvertDllsToWebcil (Utils.MoveIfDifferent) and GenerateWasmBootJson (ArtifactWriter.PersistFileIfChanged) use content-comparison write patterns that preserve old file timestamps when output content is unchanged. This defeats MSBuild's timestamp-based Inputs/Outputs incrementalism. Added <Touch> after each task invocation to ensure output timestamps reflect the current build session.

FileWrites in always-run wrapper targets

When incremental targets are skipped, <FileWrites> items inside their body are not populated. Added <FileWrites> to the always-run wrapper targets (_GenerateBuildWasmBootJson, _AddPublishWasmBootJsonToStaticWebAssets, _ResolveWasmOutputs) so dotnet clean works correctly.

Incrementalism Proof

Binlog analysis of Wasm.Browser.Sample — build 1 (clean) vs build 2 (no-op rebuild):

Build Path

Target                                   Build 1    Build 2    Status
----------------------------------------------------------------------
_ComputeWasmBuildCandidates                59ms       62ms     Runs (item producer, always needed)
_ConvertBuildDllsToWebcil                  60ms        7ms     ✅ SKIPPED — outputs up-to-date
_ResolveWasmOutputs                       138ms      116ms     Runs (DefineStaticWebAssets, item producer)
_ResolveBuildWasmBootJsonEndpoints         21ms       20ms     Runs (item producer)
_WriteBuildWasmBootJsonFile               104ms        4ms     ✅ SKIPPED — output up-to-date
_GenerateBuildWasmBootJson                  3ms        3ms     Runs (item producer)

Design Notes

Why split instead of just adding Inputs/Outputs?

MSBuild's Inputs/Outputs mechanism skips the entire target body when outputs are up-to-date. Since _ResolveWasmOutputs and _GenerateBuildWasmBootJson both contained file-writing tasks AND item-defining tasks (DefineStaticWebAssets), making them incremental would break downstream targets that depend on the items they produce. The split separates file I/O (incremental) from item definitions (always-run).

Why Touch after task execution?

Both ConvertDllsToWebcil and GenerateWasmBootJson implement "write only if content changed" patterns internally. While this avoids unnecessary downstream rebuilds, it defeats MSBuild's Inputs/Outputs check because unchanged outputs retain timestamps from a previous build session. The <Touch> ensures output timestamps always reflect the current build.

Framework asset classification

The Framework SourceType classification (from #125329) is preserved. Since the incremental _ConvertBuildDllsToWebcil target only receives DLL items, framework candidate classification is done in MSBuild item groups in _ComputeWasmBuildCandidates instead of via the task's PassThroughCandidates output. Items with WasmNativeBuildOutput metadata are per-project (Computed); items without are Framework assets needing per-project materialization.

Culture/non-culture DLL separation

Culture-specific DLLs have RelatedAsset metadata that non-culture DLLs lack. Using %(RelatedAsset) in item transforms causes MSB4096 batching errors. The fix uses separate intermediate items (_WasmWebcilConvertedNonCulture, _WasmWebcilConvertedCulture).

Testing

  • Clean build and no-op rebuild succeed with zero errors
  • No-op rebuild correctly skips _ConvertBuildDllsToWebcil and _WriteBuildWasmBootJsonFile
  • Binlog analysis verified with MSBuild.StructuredLogger

Note

This PR description was generated with assistance from GitHub Copilot.

@maraf maraf added this to the 11.0.0 milestone Mar 10, 2026
@maraf maraf self-assigned this Mar 10, 2026
@maraf maraf added arch-wasm WebAssembly architecture area-Build-mono labels Mar 10, 2026
Copilot AI review requested due to automatic review settings March 10, 2026 08:18
@maraf maraf added the os-browser Browser variant of arch-wasm label Mar 10, 2026

This comment was marked as resolved.

This comment was marked as resolved.

Copilot AI review requested due to automatic review settings March 12, 2026 20:44

This comment was marked as resolved.

Split monolithic targets into incremental chains:

Webcil conversion:
- _ComputeWasmBuildCandidates (always runs): resolves candidates, classifies DLLs
  vs framework pass-throughs, computes expected webcil output paths
- _ConvertBuildDllsToWebcil (incremental): DLL-to-webcil conversion with
  Inputs/Outputs, Touch to fix content-comparison timestamp preservation
- _ResolveWasmOutputs (always runs): reconstructs webcil items, classifies
  framework candidates, defines static web assets

Build boot JSON:
- _ResolveBuildWasmBootJsonEndpoints (always runs): endpoint resolution
- _WriteBuildWasmBootJsonFile (incremental): JSON file generation with
  Inputs/Outputs, Touch for timestamp fix
- _GenerateBuildWasmBootJson (always runs): static web asset registration

Publish boot JSON:
- _ResolvePublishWasmBootJsonInputs (always runs): input resolution
- GeneratePublishWasmBootJson (incremental): JSON file generation with
  Inputs/Outputs, Touch for timestamp fix

FileWrites are added to always-run wrapper targets so dotnet clean
works correctly even when incremental targets are skipped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@maraf maraf force-pushed the maraf/WasmSdkIncremental branch from 084b5ee to ab57788 Compare April 1, 2026 11:42
@github-actions

This comment has been minimized.

Two new tests verify WASM build incrementalism via binlog analysis:

- IncrementalBuild_NoChanges_SkipsWebcilAndBootJson: Builds twice with
  no changes, asserts _ConvertBuildDllsToWebcil and _WriteBuildWasmBootJsonFile
  are skipped on the second build.

- IncrementalBuild_SourceChange_RunsWebcilForAppOnly: Builds, modifies
  a C# source file, rebuilds, then asserts the webcil/boot JSON targets
  run but only the app assembly is re-converted (framework DLLs skipped
  by the task's internal per-file timestamp check).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 1, 2026 11:57

This comment was marked as resolved.

@github-actions

This comment has been minimized.

maraf and others added 2 commits April 1, 2026 14:09
…amps

- Add missing file inputs to _WriteBuildWasmBootJsonFile: VFS assets, config files, and dotnet.js template
- Add property stamp files for both build and publish boot JSON targets using WriteOnlyWhenDifferent to detect property-only changes (e.g., WasmDebugLevel, environment name, globalization flags)
- Build stamp: wasm-bootjson-build.stamp
- Publish stamp: wasm-bootjson-publish.stamp

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 1, 2026 14:27

This comment was marked as resolved.

The WriteLinesToFile Lines parameter is an item-list context where
@() item transforms cannot be concatenated with string literals using
a non-semicolon separator. Pre-compute the stamp string in a
PropertyGroup (string context) and pass the resulting property to Lines.

Fixes both _WriteWasmBootJsonBuildPropertyStamp (build) and
_WriteWasmPublishBootJsonPropertyStamp (publish) targets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 24, 2026 10:08

This comment was marked as resolved.

@github-actions

This comment has been minimized.

…andaloneHosting to stamps

- Reuse %(WebcilOutputPath) metadata in _ResolveWasmOutputs instead of
  recomputing paths from _WasmBuildWebcilPath + FileName/AssetTraitValue,
  eliminating duplicated path logic and risk of future divergence.
- Add  to both build and publish boot
  JSON property stamps so standalone-vs-hosted hosting mode changes
  correctly invalidate the incremental boot JSON targets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment was marked as resolved.

Copilot AI review requested due to automatic review settings April 29, 2026 06:53
@maraf maraf marked this pull request as ready for review April 29, 2026 07:00
@maraf maraf requested a review from a team April 29, 2026 07:00

This comment was marked as resolved.

Add two new test cases to RebuildTests:
- IncrementalBuild_NoChanges_SkipsWebcilAndBootJson_AOT: verifies AOT
  publish incremental skip of boot JSON generation on no-op rebuild
- IncrementalBuild_PropertyChange_RunsBootJson: verifies that changing
  WasmDebugLevel (property-only, no source changes) invalidates the
  boot JSON stamp file and triggers regeneration while skipping webcil

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread src/mono/wasm/Wasm.Build.Tests/RebuildTests.cs
Comment thread src/mono/wasm/Wasm.Build.Tests/RebuildTests.cs
@pavelsavara
Copy link
Copy Markdown
Member

Does change of (wasm related) properties force full rebuild ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-Build-mono os-browser Browser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants