Skip to content

Fix WASM boot config ContentRoot to use IntermediateOutputPath - #124125

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

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

Conversation

@lewing

@lewinglewing commented Feb 7, 2026

Copy link
Copy Markdown
Member

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 DefineStaticWebAssetsContentRoot 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

#ApproachBuild ResultWhy
1-3Boot config + preload only✅ PassNo WebCil changes
4-5Per-item ContentRoot (build only)❌ FailBuild works, but publish has no ContentRoot → MSB3030
6Task-level IntermediateOutputPath❌ FailRuntime pack files outside IntermediateOutputPath → synthesized Identity → MSB3030
7Per-item ContentRoot (build + publish)🔄 PendingThis commit — applies the fix to both build and publish

Fixesdotnet/aspnetcore#65271

CopilotAI review requested due to automatic review settings February 7, 2026 03:23
@github-actionsgithub-actionsBot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Feb 7, 2026

CopilotAI 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

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 DefineStaticWebAssetsContentRoot for the build boot config asset from $(OutDir)wwwroot to $(IntermediateOutputPath).

@lewing
lewing requested a review from javiercnFebruary 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 marafFebruary 7, 2026 03:49
@lewinglewing assigned javiercn and unassigned javiercn and lewingFeb 7, 2026
@lewinglewing 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
CopilotAI review requested due to automatic review settings February 7, 2026 16:43

CopilotAI 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

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

lewing commented Feb 8, 2026

Copy link
Copy Markdown
MemberAuthor

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)
ScenarioServer2022Server2025
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.
CopilotAI review requested due to automatic review settings February 9, 2026 15:10

CopilotAI 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

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

Comment threadsrc/tasks/WasmBuildTasks/LinkContentToWwwroot.cs
…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
lewingforce-pushed the fix/wasm-compression-stale-files branch from 420afbe to 80ab512CompareFebruary 26, 2026 23:15
@lewing

Copy link
Copy Markdown
MemberAuthor

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>
CopilotAI review requested due to automatic review settings February 27, 2026 23:21

CopilotAI 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

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

lewingand 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>
CopilotAI review requested due to automatic review settings February 28, 2026 17:40

CopilotAI 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

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

lewingand others added 2 commits March 1, 2026 12:04

CopilotAI 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

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
Copy Markdown
MemberAuthor

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

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-Metaos-browserBrowser variant of arch-wasm

Projects

None yet

5 participants

@lewing@kg@maraf@javiercn