[browser] Wasm boot JSON: touch a stamp, not the boot JSON itself (fix #129280)#129591
[browser] Wasm boot JSON: touch a stamp, not the boot JSON itself (fix #129280)#129591lewing wants to merge 2 commits into
Conversation
The build and publish boot JSON targets used <Touch Files="$(_WasmBuildBootJsonPath)" /> to give MSBuild's Inputs/Outputs check a current output timestamp despite GenerateWasmBootJson's content-preservation behavior (PR dotnet#125367). However the boot JSON ($(_WasmBuildBootJsonPath), typically obj/.../dotnet.js, and the publish equivalent) is also a source for the StaticWebAssets compression pipeline and the PreserveNewest copy target. Bumping its mtime on every successful build defeats their incrementality checks and breaks `dotnet build -question`: Building target "_BuildCopyStaticWebAssetsPreserveNewest" partially, because some output files are out of date with respect to their input files. [_BuildStaticWebAssetsPreserveNewest: Input = obj/.../compressed/{hash}-{0}-{fp}-{fp}.gz, Output = bin/.../wwwroot/_framework/dotnet.{fp}.js.gz] Input file is newer than output file. This is the same regression that PR dotnet#118637 (Aug 2025) originally fixed by introducing ArtifactWriter.PersistFileIfChanged. PR dotnet#125367's <Touch> on the boot JSON re-broke it by bumping the file's mtime unconditionally after every successful run. Replace the <Touch> on the boot JSON with a <Touch> on a separate `wasm-bootjson-{build,publish}.complete.stamp` file, and use that stamp as the target's Outputs=. MSBuild now sees a current output timestamp for its own incrementality check on the boot-JSON target, without touching the boot JSON itself — preserving content-mtime semantics for downstream consumers. Fixes dotnet#129280 Fixes the downstream cascade observed on PR dotnet#129454 (WBT `TypeScriptDefinitionsCopiedToWwwrootOnBuild(Debug, emitTypeScriptDts:True)`). Same fix applied to both the build (`_WriteBuildWasmBootJsonFile`) and publish (`GeneratePublishWasmBootJson`) paths since both have identical shape and the same downstream consumers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the WebAssembly Browser MSBuild targets to avoid touching the boot JSON itself (which can cascade timestamp changes into StaticWebAssets compression/copy), and instead uses a separate “complete” stamp file as the incremental Outputs= marker for boot JSON generation targets.
Changes:
- Switch
_WriteBuildWasmBootJsonFileandGeneratePublishWasmBootJsonto usewasm-bootjson-{build,publish}.complete.stampasOutputs=. - Touch the new stamp files (instead of the boot JSON) to satisfy MSBuild’s timestamp-based incremental checks.
- Track the new stamp files in
FileWrites(note: build wrapper update suggested; publish wrapper update still needed).
| build defeats their incrementality checks and breaks `dotnet build -question` (see | ||
| dotnet/runtime#129280, dotnet/runtime#124729, dotnet/aspnetcore#63207). | ||
| Touch a separate stamp file that is the target's Outputs= so MSBuild sees a current | ||
| output timestamp without affecting downstream consumers of the boot JSON. --> | ||
| <Touch Files="$(IntermediateOutputPath)wasm-bootjson-build.complete.stamp" AlwaysCreate="true" /> |
There was a problem hiding this comment.
Addressed in 97a975c — added wasm-bootjson-{build,publish}.complete.stamp to FileWrites in _GenerateBuildWasmBootJson and _AddPublishWasmBootJsonToStaticWebAssets so dotnet clean removes the stamps even when the incremental targets skip.
Note
This comment was AI/Copilot-generated.
| <!-- See _WriteBuildWasmBootJsonFile for rationale: touch a separate stamp file rather than | ||
| the boot JSON itself to avoid cascading mtime bumps into downstream StaticWebAssets | ||
| compression / copy targets. --> | ||
| <Touch Files="$(IntermediateOutputPath)wasm-bootjson-publish.complete.stamp" AlwaysCreate="true" /> |
There was a problem hiding this comment.
Addressed in 97a975c — added wasm-bootjson-{build,publish}.complete.stamp to FileWrites in _GenerateBuildWasmBootJson and _AddPublishWasmBootJsonToStaticWebAssets so dotnet clean removes the stamps even when the incremental targets skip.
Note
This comment was AI/Copilot-generated.
Addresses Copilot review feedback on dotnet#129591: the .complete.stamp files added in the prior commit are only listed in FileWrites inside the incremental targets (_WriteBuildWasmBootJsonFile, GeneratePublishWasmBootJson). When those targets are skipped on incremental builds, the in-body <ItemGroup> entries are not populated, so `dotnet clean` would leave the stamps behind. Mirror the existing pattern from PR dotnet#125367: also add the stamp paths to FileWrites in the always-run wrapper targets (_GenerateBuildWasmBootJson and _AddPublishWasmBootJsonToStaticWebAssets) that track the boot JSON itself for the same reason. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Problem
Wasm.Build.Tests.WasmTemplateTests.TypeScriptDefinitionsCopiedToWwwrootOnBuild(config: Debug, emitTypeScriptDts: True)fails itsdotnet build -questionrebuild check with:This is the same family of bug as #129280 (different surface: upstream variant where MSBuild flags
_WriteBuildWasmBootJsonFileitself as stale; ours is the downstream cascade through compression).Root cause
Binlog analysis:
_WriteBuildWasmBootJsonFilerunsGenerateWasmBootJson(usesArtifactWriter.PersistFileIfChanged, preserves old mtime on unchanged content) followed by<Touch Files="$(_WasmBuildBootJsonPath)" />(added in [browser] WebAssembly SDK targets more incremental #125367 to keep MSBuild's I/O check on this target happy).$(_WasmBuildBootJsonPath)—obj/{cfg}/{tfm}/dotnet.js— is also a source for the StaticWebAssets compression pipeline (GenerateBuildCompressedStaticWebAssets→GZipCompress).GZipCompress.csskips wheninput.mtime < output.mtime(strict<). WhenTouchlands close enough in time to the.gzwrite (or any subsequent target re-stamps dotnet.js), equal/newer mtimes send the next build's compression task down the re-compress path.obj/.../{0}.gzmtime pastbin/.../dotnet.{fp}.js.gz(which was copied during build WIP: repo consolidation scouting kick-off - make clr build locally on Windows #1 and isn't touched since), so_BuildCopyStaticWebAssetsPreserveNewestreports its input newer than its output.-questionfails.This is a regression of #118637 (Aug 2025, "Override boot config only when the content changes"), which fixed the exact same
_BuildCopyStaticWebAssetsPreserveNewestsymptom in dotnet/aspnetcore#63207 by introducingArtifactWriter.PersistFileIfChanged. PR #125367's<Touch>undid that protection.Fix
Touch a separate
wasm-bootjson-{build,publish}.complete.stampfile rather than the boot JSON itself, and use the stamp as the target'sOutputs=. MSBuild's incrementality check on the boot-JSON target stays correct (its declared output has a current mtime after every successful run), while the boot JSON's mtime remains content-derived — preserving #118637's invariant for downstream consumers.Applied to both:
_WriteBuildWasmBootJsonFile(build)GeneratePublishWasmBootJson(publish)Both have identical shape and the same downstream consumers (StaticWebAssets compression / Copy targets).
What's not fixed here
_ConvertBuildDllsToWebcil(line 431) uses the same<Touch>pattern but on per-item-batched@(_WasmConvertedWebcilOutputs). It is probably exposed to the same cascade for the webcil files; not addressed here pending a per-item analysis.dotnet/sdk:GZipCompress/BrotliCompress<mtime check could become<=to harden the entire StaticWebAssets pipeline against this class of cascade. Worth a separate dotnet/sdk PR.Verification
dotnet/runtimePR [main] Source code updates from dotnet/dotnet #129454 build https://dev.azure.com/dnceng-public/public/_build/results?buildId=1470806 (browser-wasm windows Release WasmBuildTests, workitemWBT-NoWebcil-MONO-ST-Wasm.Build.Tests.WasmTemplateTests).TypeScriptDefinitionsCopiedToWwwrootOnBuild(Debug|Release, emitTypeScriptDts:True)exercises thedotnet build -questionsecond-build check that this fix addresses.Fixes #129280
Note
This pull request was created with the assistance of GitHub Copilot.