Skip to content

Fix WASM boot config ContentRoot to use IntermediateOutputPath#124125

Merged
lewing merged 18 commits intodotnet:mainfrom
lewing:fix/wasm-compression-stale-files
Mar 2, 2026
Merged

Fix WASM boot config ContentRoot to use IntermediateOutputPath#124125
lewing merged 18 commits intodotnet:mainfrom
lewing:fix/wasm-compression-stale-files

Conversation

@lewing
Copy link
Member

@lewing lewing commented Feb 7, 2026

Summary

Fix a .NET 11 regression causing SRI integrity failures during incremental Blazor WASM builds. Changes in Microsoft.NET.Sdk.WebAssembly.Browser.targets:

  1. Boot config ContentRoot: Change the boot config's DefineStaticWebAssets ContentRoot from $(OutDir)wwwroot to $(IntermediateOutputPath)
  2. Preload matching: Replace fragile %(FileName)%(Extension)-based scanning with direct references to the boot config output items
  3. WebCil ContentRoot (build-time): Use per-item ContentRoot="%(RootDir)%(Directory)" on _WebCilAssetsCandidates so each asset's Identity resolves to its actual file path on disk
  4. WebCil ContentRoot (publish-time): Add per-item ContentRoot="%(RootDir)%(Directory)" on both _NewWebCilPublishStaticWebAssetsCandidatesNoMetadata and _PromotedWasmPublishStaticWebAssets — the same fix applied to publish candidates

Regression

This is a regression in .NET 11 (works in 10.0). It was introduced by dotnet/sdk#52283, which fixed an esproj compression bug by flipping the order in AssetToCompress.TryFindInputFilePath to prefer RelatedAsset (Identity) over RelatedAssetOriginalItemSpec. That fix was correct for esproj, but exposed a latent issue in the WASM SDK targets: the boot config and webcil assets' Identity pointed to a wwwroot copy rather than the actual source files.

Before sdk#52283, OriginalItemSpec happened to point to the real file and was checked first, masking the wrong ContentRoot. After the flip, RelatedAsset (Identity) is checked first, and its stale wwwroot path is used — producing incorrect SRI hashes on incremental builds.

Reported in aspnetcore#65271.

Problem

The WASM boot config file (e.g. dotnet.boot.js) is generated at $(IntermediateOutputPath) (the obj/ folder), but its static web asset was defined with ContentRoot="$(OutDir)wwwroot". This caused DefineStaticWebAssets to compute an Identity pointing to the wwwroot copy rather than the actual file in obj/.

The same issue applied to WebCil asset candidates — files from obj/webcil/, the runtime pack, and other directories were all defined with ContentRoot="$(OutputPath)wwwroot", producing synthetic Identities under wwwroot/ that could become stale during incremental builds.

Fix

1. Boot config ContentRoot

Change ContentRoot to $(IntermediateOutputPath) so the asset Identity matches the real file location on disk. The CopyToOutputDirectory="PreserveNewest" attribute still ensures the file is copied to wwwroot for serving.

This follows Javier's suggestion in dotnet/sdk#52847 to "stop defining these assets with an item spec in the wwwroot folder and just define them in their original location on disk".

2. Preload matching simplification

The _AddWasmPreloadBuildProperties and _AddWasmPreloadPublishProperties targets previously scanned all @(StaticWebAsset) items by %(FileName)%(Extension) to find the boot config asset. This relied on the Identity path containing the fingerprint in the filename, which is an implementation detail of how DefineStaticWebAssets computes Identity based on ContentRoot.

The fix replaces the scanning with direct references to @(_WasmBuildBootConfigStaticWebAsset) and @(_WasmPublishBootConfigStaticWebAsset) — the output items already produced by DefineStaticWebAssets. This is both correct and simpler.

3. WebCil per-item ContentRoot (build-time)

Instead of using a single task-level ContentRoot parameter (which forces all candidates through the same ContentRoot path, causing synthesized Identity for files outside that directory), set per-item ContentRoot="%(RootDir)%(Directory)" on each _WebCilAssetsCandidates item. This means:

  • WebCil-converted files in obj/webcil/ → ContentRoot = their parent dir → FullPath.StartsWith(ContentRoot) = true → Identity = real FullPath ✅
  • Runtime pack files (e.g. dotnet.native.js, ICU .dat) → ContentRoot = their parent dir in the runtime pack → FullPath.StartsWith(ContentRoot) = true → Identity = real FullPath ✅

No synthesized paths, no CopyCandidate entries — every asset's Identity is its actual file on disk.

4. WebCil per-item ContentRoot (publish-time)

The publish DefineStaticWebAssets call in ProcessPublishFilesForWasm previously had no ContentRoot at all — neither task-level nor per-item. This caused publish candidates (especially promoted build assets with fingerprint placeholders in their RelativePath) to have their fingerprinted filename baked into the item spec as Identity, producing paths like dotnet.native.7z98fd2ohl.wasm that don't exist on disk → MSB3030 "Could not copy file".

The fix adds per-item ContentRoot="%(RootDir)%(Directory)" on both:

  • _NewWebCilPublishStaticWebAssetsCandidatesNoMetadata (freshly WebCil-converted publish files)
  • _PromotedWasmPublishStaticWebAssets (build assets promoted to publish)

Why this works: Promoted assets carry AssetKind=Build from the build-time DefineStaticWebAssets. In DefineStaticWebAssets.cs line 252: IsPublish("Build") = false, so contentRoot is NOT nulled for publish. The per-item ContentRoot = each file's parent directory → candidateFullPath.StartsWith(contentRoot) = true → computed=false → Identity = real FullPath on disk.

What's not changed

  • Publish boot config ContentRoot (ContentRoot="$(PublishDir)wwwroot"): Publish builds are clean and don't have the incremental staleness problem.

Build Progression

# Approach Build Result Why
1-3 Boot config + preload only ✅ Pass No WebCil changes
4-5 Per-item ContentRoot (build only) ❌ Fail Build works, but publish has no ContentRoot → MSB3030
6 Task-level IntermediateOutputPath ❌ Fail Runtime pack files outside IntermediateOutputPath → synthesized Identity → MSB3030
7 Per-item ContentRoot (build + publish) 🔄 Pending This commit — applies the fix to both build and publish

Fixes dotnet/aspnetcore#65271

Copilot AI review requested due to automatic review settings February 7, 2026 03:23
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Feb 7, 2026
Copy link
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

Updates the Blazor WASM SDK targets to define the build boot config static web asset from its actual on-disk generation location (obj/) to prevent stale-asset Identity selection during incremental builds (which can lead to incorrect SRI integrity values at runtime).

Changes:

  • Change DefineStaticWebAssets ContentRoot for the build boot config asset from $(OutDir)wwwroot to $(IntermediateOutputPath).

@lewing lewing requested a review from javiercn February 7, 2026 03:33
@lewing lewing marked this pull request as ready for review February 7, 2026 03:33
@lewing lewing requested a review from akoeplinger as a code owner February 7, 2026 03:33
@lewing lewing requested a review from maraf February 7, 2026 03:49
@lewing lewing assigned javiercn and unassigned javiercn and lewing Feb 7, 2026
@lewing lewing added arch-wasm WebAssembly architecture area-Meta os-browser Browser variant of arch-wasm and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Feb 7, 2026
Copilot AI review requested due to automatic review settings February 7, 2026 16:43
Copy link
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 1 out of 1 changed files in this pull request and generated no new comments.

lewing added a commit that referenced this pull request Feb 8, 2026
Canceled AzDO jobs (typically from timeouts) still have pipeline
artifacts containing binlogs. The SendToHelix.binlog contains Helix
job IDs that can be queried directly to recover actual test results.

Discovered while investigating PR #124125 where a 3-hour timeout
caused a WasmBuildTests job to be canceled, but all 226 Helix work
items had actually passed.
lewing added a commit that referenced this pull request Feb 8, 2026
Canceled AzDO jobs (typically from timeouts) still have pipeline
artifacts containing binlogs. The SendToHelix.binlog contains Helix
job IDs that can be queried directly to recover actual test results.

Discovered while investigating PR #124125 where a 3-hour timeout
caused a WasmBuildTests job to be canceled, but all 226 Helix work
items had actually passed.
@lewing
Copy link
Member Author

lewing commented Feb 8, 2026

Canceled WasmBuildTests actually passed ✅

The browser-wasm windows Release WasmBuildTests job in build 1284169 was canceled after the 3-hour timeout, but all Helix work items completed successfully.

Recovery steps:

  1. Downloaded the Logs_Build_Attempt1_browser_wasm_windows_Release_WasmBuildTests pipeline artifact
  2. Loaded the truncated \SendToHelix.binlog\ and extracted 12 Helix job IDs
  3. Queried each job via Helix API — all 226 work items passed (0 failures)
Scenario Server2022 Server2025
NoWorkload-ST ✅ 2/2 ✅ 2/2
NoWebcil-ST ✅ 53/53 + 2/2 ✅ 53/53 + 2/2
Workloads-ST ✅ 53/53 ✅ 53/53
NoFingerprint-ST ✅ 2/2 + 2/2 ✅ 2/2 + 2/2

The failure was purely the AzDO job wrapper timing out while waiting to collect results, not an actual test failure.

lewing added a commit that referenced this pull request Feb 8, 2026
Canceled AzDO jobs (typically from timeouts) still have pipeline
artifacts containing binlogs. The \SendToHelix.binlog\ contains Helix
job IDs that can be queried directly to recover actual test results.

## What changed
- Added **Recovering Results from Canceled Jobs** section to helix skill
SKILL.md
- Documents the workflow: download artifacts → load binlog → extract
Helix job IDs → query Helix API
- Includes concrete example from PR #124125 (226 work items all passed
despite 3h timeout cancellation)
- Added tip about binlog MCP server for structured binlog analysis

## Context
Discovered while investigating #124125 where \�rowser-wasm windows
Release WasmBuildTests\ was canceled after 3 hours. The script reported
no failures (correct — no *failed* jobs) but also couldn't surface that
the tests actually passed. With the documented recovery workflow, the
Helix results were fully recoverable.
Copilot AI review requested due to automatic review settings February 9, 2026 15:10
Copy link
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 4 out of 4 changed files in this pull request and generated 2 comments.

…ation

- ComputeWasmPublishAssets: Match RelatedAsset by full filename with extension
  instead of base name only, with webcil .wasm->.dll normalization to avoid
  ambiguity between .dll/.pdb files sharing the same base name.
- LinkContentToWwwroot: Normalize relative ItemSpec paths to absolute using
  MSBuildProjectDirectory when computing ContentRoot, preventing incorrect
  paths when working directory differs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lewing lewing force-pushed the fix/wasm-compression-stale-files branch from 420afbe to 80ab512 Compare February 26, 2026 23:15
@lewing
Copy link
Member Author

lewing commented Feb 26, 2026

Addressed CCA review feedback in latest commit:

  • Full filename matching (ComputeWasmPublishAssets.cs): UpdateRelatedAssetProperty now matches by full filename with extension (with webcil .wasm to .dll normalization) instead of base name only, preventing ambiguity between .dll/.pdb files.
  • Absolute path normalization (LinkContentToWwwroot.cs): Added Path.IsPathRooted check with Path.GetFullPath(identity, MSBuildProjectDirectory) fallback for relative paths in ContentRoot computation.
  • Hard-coded line number: Replaced (line 448) reference with (above).
  • Fingerprint/Integrity empty strings: Investigated RemoveMetadata approach but it requires Include, and using Include on the same item group duplicates all items. Empty string via Update is intentional - DefineStaticWebAssets treats it as recompute.

Resolve conflict in Microsoft.NET.Sdk.WebAssembly.Browser.targets:
- Adopt upstream Webcil casing (lowercase 'c') for property names
- Keep PR's removal of _WasmBuildOuputPath
- Fix casing in per-item ContentRoot Update lines to match

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 27, 2026 23:21
Copy link
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 4 out of 4 changed files in this pull request and generated 1 comment.

lewing and others added 2 commits February 27, 2026 21:47
The upstream Webcil casing change renamed the property to IsWebcilEnabled
(lowercase 'c'), but the conflict resolution left references using the
old IsWebCilEnabled casing, causing CS0103 build errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 28, 2026 17:40
Copy link
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 4 out of 4 changed files in this pull request and generated no new comments.

lewing and others added 2 commits March 1, 2026 12:04
Copy link
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 4 out of 4 changed files in this pull request and generated 1 comment.

PR dotnet#124905 renamed IsWebCilEnabled to IsWebcilEnabled on main.
The merge auto-resolved the parameter name but the error message
string interpolation still referenced the old casing. Also improved
the error message to include asset details and used 'is null' pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lewing
Copy link
Member Author

lewing commented Mar 2, 2026

/ba-g failure is an OOM we see without this PR too

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

Labels

arch-wasm WebAssembly architecture area-Meta os-browser Browser variant of arch-wasm

Projects

None yet

5 participants