Skip to content

Commit ab57788

Browse files
marafCopilot
andcommitted
Add MSBuild incrementalism to WASM build targets
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>
1 parent c578848 commit ab57788

1 file changed

Lines changed: 136 additions & 11 deletions

File tree

src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets

Lines changed: 136 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,9 @@ Copyright (c) .NET Foundation. All rights reserved.
289289
</ItemGroup>
290290
</Target>
291291

292-
<Target Name="_ResolveWasmOutputs" DependsOnTargets="ResolveReferences;PrepareResourceNames;ComputeIntermediateSatelliteAssemblies;_ResolveWasmConfiguration;_WasmNativeForBuild">
292+
<!-- Resolve and classify build asset candidates.
293+
This target always runs to ensure candidate items are populated for downstream consumers. -->
294+
<Target Name="_ComputeWasmBuildCandidates" DependsOnTargets="ResolveReferences;PrepareResourceNames;ComputeIntermediateSatelliteAssemblies;_ResolveWasmConfiguration;_WasmNativeForBuild">
293295
<PropertyGroup>
294296
<_WasmNativeAssetFileNames>;@(WasmNativeAsset->'%(FileName)%(Extension)');@(WasmAssembliesFinal->'%(FileName)%(Extension)');</_WasmNativeAssetFileNames>
295297
<_WasmIntermediateAssemblyFileNames Condition="@(WasmAssembliesFinal->Count()) != 0">;@(IntermediateAssembly->'%(FileName)%(Extension)');</_WasmIntermediateAssemblyFileNames>
@@ -353,17 +355,99 @@ Copyright (c) .NET Foundation. All rights reserved.
353355
<_WasmBuildTmpWebcilPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'tmp-webcil'))</_WasmBuildTmpWebcilPath>
354356
</PropertyGroup>
355357

356-
<ConvertDllsToWebcil Candidates="@(_BuildAssetsCandidates)" IntermediateOutputPath="$(_WasmBuildTmpWebcilPath)" OutputPath="$(_WasmBuildWebcilPath)" IsEnabled="$(_WasmEnableWebcil)">
357-
<Output TaskParameter="WebcilCandidates" ItemName="_WebcilAssetsCandidates" />
358-
<Output TaskParameter="PassThroughCandidates" ItemName="_WasmFrameworkCandidates" />
358+
<!-- Identify DLL candidates that need webcil conversion, separate culture from non-culture
359+
DLLs, and compute their expected output paths for use as Outputs in the incremental
360+
_ConvertBuildDllsToWebcil target. Culture and non-culture DLLs are separated into
361+
distinct item groups to avoid MSBuild batching errors on metadata (like RelatedAsset)
362+
that only culture items define.
363+
Also pre-classify non-DLL candidates: items with WasmNativeBuildOutput metadata are
364+
already per-project (Computed); items without are Framework assets needing per-project
365+
materialization. Pre-filtering avoids MSBuild batching errors on WasmNativeBuildOutput
366+
metadata that only WasmNativeAsset items define. -->
367+
<ItemGroup Condition="'$(_WasmEnableWebcil)' == 'true'">
368+
<_WasmDllBuildCandidates Include="@(_BuildAssetsCandidates)" Condition="'%(Extension)' == '.dll'" />
369+
<_WasmDllBuildCandidatesNonCulture Include="@(_WasmDllBuildCandidates)" Condition="'%(AssetTraitName)' != 'Culture'" />
370+
<_WasmDllBuildCandidatesCulture Include="@(_WasmDllBuildCandidates)" Condition="'%(AssetTraitName)' == 'Culture'" />
371+
<_WasmExpectedWebcilOutputs Include="@(_WasmDllBuildCandidatesNonCulture->'$(_WasmBuildWebcilPath)%(FileName).wasm')" />
372+
<_WasmExpectedWebcilOutputs Include="@(_WasmDllBuildCandidatesCulture->'$(_WasmBuildWebcilPath)%(AssetTraitValue)/%(FileName).wasm')" />
373+
</ItemGroup>
374+
375+
<!-- Separate non-DLL items into native build outputs (per-project, Computed) and
376+
framework candidates (shared, need materialization). WasmNativeBuildOutput metadata
377+
is only set on WasmNativeAsset items so we filter from the source to avoid batching
378+
errors on _BuildAssetsCandidates. -->
379+
<ItemGroup>
380+
<_WasmNativeBuildOutputCandidates Include="@(_BuildAssetsCandidates)" Condition="'%(Extension)' != '.dll' and '%(_BuildAssetsCandidates.WasmNativeBuildOutput)' != ''" />
381+
<_WasmNonDllNonNativeCandidates Include="@(_BuildAssetsCandidates)" Condition="'%(Extension)' != '.dll'" />
382+
<_WasmNonDllNonNativeCandidates Remove="@(_WasmNativeBuildOutputCandidates)" />
383+
</ItemGroup>
384+
</Target>
385+
386+
<!-- Convert DLL assemblies to webcil format.
387+
This target is incremental: when all input DLLs are older than their corresponding
388+
webcil outputs, the entire target is skipped, saving the conversion task overhead.
389+
The task also has internal per-file timestamp checks as a secondary optimization. -->
390+
<Target Name="_ConvertBuildDllsToWebcil"
391+
DependsOnTargets="_ComputeWasmBuildCandidates"
392+
Condition="'$(_WasmEnableWebcil)' == 'true'"
393+
Inputs="@(_WasmDllBuildCandidates);$(MSBuildProjectFullPath);$(MSBuildThisFileFullPath);$(_WebAssemblySdkTasksAssembly)"
394+
Outputs="@(_WasmExpectedWebcilOutputs)">
395+
396+
<ConvertDllsToWebcil Candidates="@(_WasmDllBuildCandidates)" IntermediateOutputPath="$(_WasmBuildTmpWebcilPath)" OutputPath="$(_WasmBuildWebcilPath)" IsEnabled="$(_WasmEnableWebcil)">
359397
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
398+
<Output TaskParameter="FileWrites" ItemName="_WasmConvertedWebcilOutputs" />
360399
</ConvertDllsToWebcil>
361400

362-
<!-- Remove pass-throughs from webcil candidates so each file is classified exactly once:
363-
webcil-converted files → Computed (per-project in obj/webcil/)
364-
pass-through files → Framework (materialized per-project by UpdatePackageStaticWebAssets) -->
365-
<ItemGroup>
366-
<_WebcilAssetsCandidates Remove="@(_WasmFrameworkCandidates)" />
401+
<!-- The ConvertDllsToWebcil task uses content comparison and preserves old timestamps when
402+
the output content is unchanged. Touch the files that were actually written so
403+
MSBuild's Inputs/Outputs check sees current timestamps and can correctly skip this
404+
target on subsequent builds. -->
405+
<Touch Files="@(_WasmConvertedWebcilOutputs)" Condition="'@(_WasmConvertedWebcilOutputs)' != ''" />
406+
</Target>
407+
408+
<!-- Resolve webcil candidate items and define static web assets for the build.
409+
This target always runs because it populates item groups consumed by downstream targets.
410+
It reconstructs the webcil candidate items from _BuildAssetsCandidates using the same
411+
path logic as the ConvertDllsToWebcil task, so items are correct whether the conversion
412+
target ran or was skipped due to incrementalism.
413+
Pass-through files are classified as Framework assets for per-project materialization. -->
414+
<Target Name="_ResolveWasmOutputs" DependsOnTargets="_ComputeWasmBuildCandidates;_ConvertBuildDllsToWebcil">
415+
416+
<!-- When webcil is enabled, transform DLL candidates to their webcil output paths and fix
417+
metadata. Non-culture and culture DLLs use separate intermediate items to avoid MSBuild
418+
batching errors on metadata (like RelatedAsset) that only culture items define.
419+
Only webcil-converted items and WasmNativeBuildOutput items go to _WebcilAssetsCandidates
420+
(Computed SourceType). Framework candidates are classified separately. -->
421+
<ItemGroup Condition="'$(_WasmEnableWebcil)' == 'true'">
422+
<_WasmWebcilConvertedNonCulture Include="@(_WasmDllBuildCandidatesNonCulture->'$(_WasmBuildWebcilPath)%(FileName).wasm')">
423+
<RelativePath>$([System.IO.Path]::ChangeExtension(%(RelativePath), '.wasm'))</RelativePath>
424+
<OriginalItemSpec>$(_WasmBuildWebcilPath)%(FileName).wasm</OriginalItemSpec>
425+
</_WasmWebcilConvertedNonCulture>
426+
<_WasmWebcilConvertedCulture Include="@(_WasmDllBuildCandidatesCulture->'$(_WasmBuildWebcilPath)%(AssetTraitValue)/%(FileName).wasm')">
427+
<RelativePath>$([System.IO.Path]::ChangeExtension(%(RelativePath), '.wasm'))</RelativePath>
428+
<OriginalItemSpec>$(_WasmBuildWebcilPath)%(AssetTraitValue)/%(FileName).wasm</OriginalItemSpec>
429+
<RelatedAsset>$([System.IO.Path]::ChangeExtension(%(RelatedAsset), '.wasm'))</RelatedAsset>
430+
</_WasmWebcilConvertedCulture>
431+
432+
<!-- Webcil-converted items + WasmNativeBuildOutput items → Computed (per-project already) -->
433+
<_WebcilAssetsCandidates Include="@(_WasmNativeBuildOutputCandidates)" />
434+
<_WebcilAssetsCandidates Include="@(_WasmWebcilConvertedNonCulture)" />
435+
<_WebcilAssetsCandidates Include="@(_WasmWebcilConvertedCulture)" />
436+
437+
<!-- Non-DLL items without WasmNativeBuildOutput → Framework (need per-project materialization) -->
438+
<_WasmFrameworkCandidates Include="@(_WasmNonDllNonNativeCandidates)" />
439+
440+
<!-- Track webcil files for clean operations even when the conversion target was skipped -->
441+
<FileWrites Include="@(_WasmExpectedWebcilOutputs)" />
442+
</ItemGroup>
443+
444+
<!-- When webcil is disabled, DLLs retain their shared paths and also need Framework
445+
materialization (along with non-DLL items). Only WasmNativeBuildOutput items are
446+
already per-project (Computed). -->
447+
<ItemGroup Condition="'$(_WasmEnableWebcil)' != 'true'">
448+
<_WebcilAssetsCandidates Include="@(_WasmNativeBuildOutputCandidates)" />
449+
<_WasmFrameworkCandidates Include="@(_WasmNonDllNonNativeCandidates)" />
450+
<_WasmFrameworkCandidates Include="@(_BuildAssetsCandidates)" Condition="'%(Extension)' == '.dll'" />
367451
</ItemGroup>
368452

369453
<ItemGroup>
@@ -480,7 +564,9 @@ Copyright (c) .NET Foundation. All rights reserved.
480564
</ItemGroup>
481565
</Target>
482566

483-
<Target Name="_GenerateBuildWasmBootJson" DependsOnTargets="$(GenerateBuildWasmBootJsonDependsOn)">
567+
<!-- Resolve static web asset endpoints for the boot JSON generation.
568+
This target always runs to compute JS module candidates and wasm asset endpoints. -->
569+
<Target Name="_ResolveBuildWasmBootJsonEndpoints" DependsOnTargets="$(GenerateBuildWasmBootJsonDependsOn)">
484570
<PropertyGroup>
485571
<_WasmBuildBootJsonPath>$(IntermediateOutputPath)$(_WasmBootConfigFileName)</_WasmBuildBootJsonPath>
486572
<_WasmBuildApplicationEnvironmentName>$(WasmApplicationEnvironmentName)</_WasmBuildApplicationEnvironmentName>
@@ -534,6 +620,15 @@ Copyright (c) .NET Foundation. All rights reserved.
534620
>
535621
<Output TaskParameter="ResolvedEndpoints" ItemName="_WasmResolvedEndpoints" />
536622
</ResolveFingerprintedStaticWebAssetEndpointsForAssets>
623+
</Target>
624+
625+
<!-- Write the boot JSON file for the build.
626+
This target is incremental: when all inputs (assemblies, static web assets, config files,
627+
extensions) are older than the output boot JSON file, the entire target is skipped. -->
628+
<Target Name="_WriteBuildWasmBootJsonFile"
629+
DependsOnTargets="_ResolveBuildWasmBootJsonEndpoints"
630+
Inputs="@(IntermediateAssembly);@(WasmStaticWebAsset);@(_WasmJsModuleCandidatesForBuild);@(WasmBootConfigExtension);$(ProjectRuntimeConfigFilePath);$(MSBuildProjectFullPath);$(MSBuildThisFileFullPath);$(_WebAssemblySdkTasksAssembly)"
631+
Outputs="$(_WasmBuildBootJsonPath)">
537632

538633
<GenerateWasmBootJson
539634
AssemblyPath="@(IntermediateAssembly)"
@@ -575,7 +670,19 @@ Copyright (c) .NET Foundation. All rights reserved.
575670
<FileWrites Include="$(_WasmBuildBootJsonPath)" />
576671
</ItemGroup>
577672

673+
<!-- The GenerateWasmBootJson task uses content comparison and preserves old timestamps when
674+
the output content is unchanged. Touch the output so MSBuild's Inputs/Outputs check
675+
sees a current timestamp and can correctly skip this target on subsequent builds. -->
676+
<Touch Files="$(_WasmBuildBootJsonPath)" />
677+
</Target>
678+
679+
<!-- Define the boot config file as a static web asset and create endpoints.
680+
This target always runs to ensure items are populated even when _WriteBuildWasmBootJsonFile is skipped. -->
681+
<Target Name="_GenerateBuildWasmBootJson" DependsOnTargets="_WriteBuildWasmBootJsonFile">
682+
578683
<ItemGroup>
684+
<!-- Track boot JSON for clean operations even when _WriteBuildWasmBootJsonFile was skipped -->
685+
<FileWrites Include="$(_WasmBuildBootJsonPath)" />
579686
<_WasmBuildBootConfigCandidate
580687
Include="$(_WasmBuildBootJsonPath)"
581688
RelativePath="_framework/$(_WasmBootConfigFileName)" />
@@ -887,6 +994,8 @@ Copyright (c) .NET Foundation. All rights reserved.
887994
<Target Name="_AddPublishWasmBootJsonToStaticWebAssets" DependsOnTargets="GeneratePublishWasmBootJson">
888995

889996
<ItemGroup>
997+
<!-- Track boot JSON for clean operations even when GeneratePublishWasmBootJson was skipped -->
998+
<FileWrites Include="$(IntermediateOutputPath)$(_WasmPublishBootConfigFileName)" />
890999
<_WasmPublishBootConfigCandidate
8911000
Include="$(IntermediateOutputPath)$(_WasmPublishBootConfigFileName)"
8921001
RelativePath="_framework/$(_WasmBootConfigFileName)" />
@@ -924,7 +1033,9 @@ Copyright (c) .NET Foundation. All rights reserved.
9241033

9251034
</Target>
9261035

927-
<Target Name="GeneratePublishWasmBootJson" DependsOnTargets="$(GeneratePublishWasmBootJsonDependsOn)">
1036+
<!-- Resolve inputs for the publish boot JSON target.
1037+
This target always runs to compute publish asset items and endpoints. -->
1038+
<Target Name="_ResolvePublishWasmBootJsonInputs" DependsOnTargets="$(GeneratePublishWasmBootJsonDependsOn)">
9281039
<PropertyGroup>
9291040
<_WasmPublishApplicationEnvironmentName>$(WasmApplicationEnvironmentName)</_WasmPublishApplicationEnvironmentName>
9301041
</PropertyGroup>
@@ -957,6 +1068,15 @@ Copyright (c) .NET Foundation. All rights reserved.
9571068
>
9581069
<Output TaskParameter="ResolvedEndpoints" ItemName="_WasmResolvedEndpointsForPublish" />
9591070
</ResolveFingerprintedStaticWebAssetEndpointsForAssets>
1071+
</Target>
1072+
1073+
<!-- Write the publish boot JSON file.
1074+
This target is incremental: when all inputs (assemblies, publish assets, config files,
1075+
extensions) are older than the output, the target is skipped. -->
1076+
<Target Name="GeneratePublishWasmBootJson"
1077+
DependsOnTargets="_ResolvePublishWasmBootJsonInputs"
1078+
Inputs="@(IntermediateAssembly);@(_WasmPublishAsset);@(_WasmJsModuleCandidatesForPublish);@(_WasmPublishConfigFile);@(_WasmDotnetJsForPublish);@(WasmBootConfigExtension);$(ProjectRuntimeConfigFilePath);$(MSBuildProjectFullPath);$(MSBuildThisFileFullPath);$(_WebAssemblySdkTasksAssembly)"
1079+
Outputs="$(IntermediateOutputPath)$(_WasmPublishBootConfigFileName)">
9601080

9611081
<GenerateWasmBootJson
9621082
AssemblyPath="@(IntermediateAssembly)"
@@ -998,6 +1118,11 @@ Copyright (c) .NET Foundation. All rights reserved.
9981118
<FileWrites Include="$(IntermediateOutputPath)$(_WasmPublishBootConfigFileName)" />
9991119
</ItemGroup>
10001120

1121+
<!-- The GenerateWasmBootJson task uses content comparison and preserves old timestamps when
1122+
the output content is unchanged. Touch the output so MSBuild's Inputs/Outputs check
1123+
sees a current timestamp and can correctly skip this target on subsequent builds. -->
1124+
<Touch Files="$(IntermediateOutputPath)$(_WasmPublishBootConfigFileName)" />
1125+
10011126
</Target>
10021127

10031128
<Target Name="_WasmNative"

0 commit comments

Comments
 (0)