[release/6.0][wasm] Add support for native relinking after Build, and AOT after publi...
authorAnkit Jain <radical@gmail.com>
Wed, 15 Sep 2021 13:16:48 +0000 (13:16 +0000)
committerGitHub <noreply@github.com>
Wed, 15 Sep 2021 13:16:48 +0000 (15:16 +0200)
59 files changed:
eng/Versions.props
eng/testing/scenarios/BuildWasmAppsJobsList.txt
eng/testing/tests.mobile.targets
eng/testing/tests.wasm.targets
src/libraries/sendtohelixhelp.proj
src/libraries/workloads-testing.targets
src/mono/sample/Android/AndroidSampleApp.csproj
src/mono/sample/iOS/Program.csproj
src/mono/sample/mbr/browser/WasmDelta.csproj
src/mono/sample/wasm/Directory.Build.targets
src/mono/sample/wasm/console/Program.cs
src/mono/sample/wasm/wasm.mk
src/mono/wasm/BlazorOverwrite.targets [new file with mode: 0644]
src/mono/wasm/build/README.md
src/mono/wasm/build/WasmApp.InTree.targets
src/mono/wasm/build/WasmApp.LocalBuild.targets
src/mono/wasm/build/WasmApp.Native.targets
src/mono/wasm/build/WasmApp.props
src/mono/wasm/build/WasmApp.targets
src/mono/wasm/data/aot-tests/ProxyProjectForAOTOnHelix.proj
src/mono/wasm/debugger/tests/Directory.Build.props
src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj
src/tasks/AotCompilerTask/MonoAOTCompiler.cs
src/tasks/Common/Utils.cs
src/tasks/WasmAppBuilder/EmccCompile.cs
src/tasks/WasmAppBuilder/IcallTableGenerator.cs
src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs
src/tasks/WasmAppBuilder/WasmAppBuilder.cs
src/tasks/WasmAppBuilder/WasmAppBuilder.csproj
src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs [new file with mode: 0644]
src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/BuildAndRunAttribute.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/BuildPublishTests.cs [new file with mode: 0644]
src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/CleanTests.cs [new file with mode: 0644]
src/tests/BuildWasmApps/Wasm.Build.Tests/CommandResult.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/HelperExtensions.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/ReferenceNewAssemblyRebuildTest.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/SimpleSourceChangeRebuildTest.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/SatelliteAssembliesTests.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/SharedBuildPerTestClassFixture.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj
src/tests/BuildWasmApps/Wasm.Build.Tests/WasmNativeDefaultsTests.cs [new file with mode: 0644]
src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets
src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.props [new file with mode: 0644]
src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.targets [new file with mode: 0644]
src/tests/BuildWasmApps/Wasm.Build.Tests/data/Local.Directory.Build.props
src/tests/BuildWasmApps/Wasm.Build.Tests/data/Local.Directory.Build.targets
src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets
src/tests/Common/wasm-test-runner/WasmTestRunner.proj
src/tests/FunctionalTests/WebAssembly/Browser/HotReload/WebAssembly.Browser.HotReload.Test.csproj
src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/WebAssembly.Browser.RuntimeConfig.Test.csproj
src/tests/FunctionalTests/WebAssembly/Directory.Build.targets

index e6617f9..8f060f6 100644 (file)
     <SQLitePCLRawbundle_greenVersion>2.0.4</SQLitePCLRawbundle_greenVersion>
     <MoqVersion>4.12.0</MoqVersion>
     <FsCheckVersion>2.14.3</FsCheckVersion>
-    <SdkVersionForWorkloadTesting>6.0.100-rc.2.21425.12</SdkVersionForWorkloadTesting>
+    <SdkVersionForWorkloadTesting>6.0.100-rc.2.21463.12</SdkVersionForWorkloadTesting>
     <!-- Docs -->
     <MicrosoftPrivateIntellisenseVersion>5.0.0-preview-20201009.2</MicrosoftPrivateIntellisenseVersion>
     <!-- ILLink -->
index ba32227..c4c5a18 100644 (file)
@@ -1,14 +1,18 @@
-BlazorWasmTests
-FlagsChangeRebuildTest
-InvariantGlobalizationTests
-LocalEMSDKTests
-MainWithArgsTests
-NativeBuildTests
-NativeLibraryTests
-NoopNativeRebuildTest
-RebuildTests
-ReferenceNewAssemblyRebuildTest
-SatelliteAssembliesTests
-SimpleSourceChangeRebuildTest
-WasmBuildAppTest
-WorkloadTests
+Wasm.Build.NativeRebuild.Tests.FlagsChangeRebuildTest
+Wasm.Build.NativeRebuild.Tests.NoopNativeRebuildTest
+Wasm.Build.NativeRebuild.Tests.ReferenceNewAssemblyRebuildTest
+Wasm.Build.NativeRebuild.Tests.SimpleSourceChangeRebuildTest
+Wasm.Build.Tests.BlazorWasmBuildPublishTests
+Wasm.Build.Tests.BlazorWasmTests
+Wasm.Build.Tests.BuildPublishTests
+Wasm.Build.Tests.CleanTests
+Wasm.Build.Tests.InvariantGlobalizationTests
+Wasm.Build.Tests.LocalEMSDKTests
+Wasm.Build.Tests.MainWithArgsTests
+Wasm.Build.Tests.NativeBuildTests
+Wasm.Build.Tests.NativeLibraryTests
+Wasm.Build.Tests.RebuildTests
+Wasm.Build.Tests.SatelliteAssembliesTests
+Wasm.Build.Tests.WasmBuildAppTest
+Wasm.Build.Tests.WasmNativeDefaultsTests
+Wasm.Build.Tests.WorkloadTests
index fc196e0..ce6bfe2 100644 (file)
@@ -7,6 +7,7 @@
 
     <PublishingTestsRun>true</PublishingTestsRun>
     <BundleTestAppTargets>BundleTestAppleApp;BundleTestAndroidApp</BundleTestAppTargets>
+    <PublishTestAsSelfContainedDependsOn>Publish</PublishTestAsSelfContainedDependsOn>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(EnableAggressiveTrimming)' == 'true'">
         OutputType="AsmOnly"
         Assemblies="@(AotInputAssemblies)"
         AotModulesTablePath="$(BundleDir)\modules.c"
+        IntermediateOutputPath="$(IntermediateOutputPath)"
         UseLLVM="$(MonoEnableLLVM)"
         LLVMPath="$(MonoAotCrossDir)">
         <Output TaskParameter="CompiledAssemblies" ItemName="BundleAssemblies" />
         Assemblies="@(AotInputAssemblies)"
         AotModulesTablePath="$(BundleDir)\modules.m"
         AotModulesTableLanguage="ObjC"
+        IntermediateOutputPath="$(IntermediateOutputPath)"
         UseLLVM="$(MonoEnableLLVM)"
         LLVMPath="$(MonoAotCrossDir)">
         <Output TaskParameter="CompiledAssemblies" ItemName="BundleAssemblies" />
   <Target Name="PublishTestAsSelfContained"
           Condition="'$(IsCrossTargetingBuild)' != 'true'"
           AfterTargets="Build"
-          DependsOnTargets="Publish;$(BundleTestAppTargets);ArchiveTests" />
+          DependsOnTargets="$(PublishTestAsSelfContainedDependsOn);$(BundleTestAppTargets);ArchiveTests" />
 
   <Target Name="PrepareForTestUsingWorkloads"
           BeforeTargets="Test"
index 386ef21..f68b4f9 100644 (file)
@@ -1,6 +1,8 @@
 <Project>
   <!-- We need to set this in order to get extensibility on xunit category traits and other arguments we pass down to xunit via MSBuild properties -->
   <PropertyGroup>
+    <IsWasmProject Condition="'$(IsWasmProject)' == ''">true</IsWasmProject>
+    <WasmGenerateAppBundle Condition="'$(WasmGenerateAppBundle)' == ''">true</WasmGenerateAppBundle>
     <BundleTestAppTargets>$(BundleTestAppTargets);BundleTestWasmApp</BundleTestAppTargets>
     <DebuggerSupport Condition="'$(DebuggerSupport)' == '' and '$(Configuration)' == 'Debug'">true</DebuggerSupport>
     <!-- Some tests expect to load satellite assemblies by path, eg. System.Runtime.Loader.Tests,
           Condition="'$(BuildAOTTestsOn)' == 'local'" />
 
   <PropertyGroup>
-      <WasmBuildAppDependsOn>PrepareForWasmBuildApp;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
+    <BundleTestWasmAppDependsOn Condition="'$(BuildAOTTestsOn)' == 'local'">WasmTriggerPublishApp</BundleTestWasmAppDependsOn>
+    <BundleTestWasmAppDependsOn Condition="'$(BuildAOTTestsOnHelix)' == 'true'">$(BundleTestWasmAppDependsOn);_BundleAOTTestWasmAppForHelix</BundleTestWasmAppDependsOn>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(BuildAOTTestsOnHelix)' == 'true'">
+    <!-- wasm targets are not imported at all, in this case, because we run the wasm build on helix -->
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(BuildAOTTestsOnHelix)' != 'true'">
+    <WasmBuildOnlyAfterPublish>true</WasmBuildOnlyAfterPublish>
 
-      <BundleTestWasmAppDependsOn Condition="'$(BuildAOTTestsOn)' == 'local'">WasmBuildApp</BundleTestWasmAppDependsOn>
-      <BundleTestWasmAppDependsOn Condition="'$(BuildAOTTestsOnHelix)' == 'true'">$(BundleTestWasmAppDependsOn);_BundleAOTTestWasmAppForHelix</BundleTestWasmAppDependsOn>
+    <!-- wasm's publish targets will trigger publish, so we shouldn't do that -->
+    <PublishTestAsSelfContainedDependsOn />
+    <WasmNestedPublishAppDependsOn>PrepareForWasmBuildApp;$(WasmNestedPublishAppDependsOn)</WasmNestedPublishAppDependsOn>
   </PropertyGroup>
 
   <ItemGroup>
@@ -80,6 +92,8 @@
       <RuntimeConfigFilePath>$([System.IO.Path]::ChangeExtension($(_MainAssemblyPath), '.runtimeconfig.json'))</RuntimeConfigFilePath>
     </PropertyGroup>
 
+    <Error Text="Item WasmAssembliesToBundle is empty. This is likely an authoring error." Condition="@(WasmAssembliesToBundle->Count()) == 0" />
+
     <ItemGroup>
       <BundleFiles Include="$(WasmMainJSPath)"                  TargetDir="publish" />
       <BundleFiles Include="@(WasmAssembliesToBundle)"          TargetDir="publish\%(WasmAssembliesToBundle.RecursiveDir)" />
     </ItemGroup>
 
     <ItemGroup>
-      <WasmAssembliesToBundle Include="$(PublishDir)\**\*.dll"/>
+      <WasmAssembliesToBundle Include="$(PublishDir)\**\*.dll" Condition="'$(BuildAOTTestsOnHelix)' == 'true'" />
       <WasmFilesToIncludeInFileSystem Include="@(ContentWithTargetPath)" />
 
       <_CopyLocalPaths
index 14ea741..93cbb9a 100644 (file)
     </PropertyGroup>
 
     <ItemGroup Condition="'$(Scenario)' == 'BuildWasmApps'">
-      <HelixWorkItem Include="@(BuildWasmApps_PerJobList->'$(WorkItemPrefix)%(FileName)')">
+      <HelixWorkItem Include="@(BuildWasmApps_PerJobList->'$(WorkItemPrefix)%(Extension)')">
         <PayloadArchive>$(_BuildWasmAppsPayloadArchive)</PayloadArchive>
-        <PreCommands Condition="'$(OS)' == 'Windows_NT'">set &quot;HELIX_XUNIT_ARGS=-class Wasm.Build.Tests.%(Identity)&quot;</PreCommands>
-        <PreCommands Condition="'$(OS)' != 'Windows_NT'">export &quot;HELIX_XUNIT_ARGS=-class Wasm.Build.Tests.%(Identity)&quot;</PreCommands>
+        <PreCommands Condition="'$(OS)' == 'Windows_NT'">set &quot;HELIX_XUNIT_ARGS=-class %(Identity)&quot;</PreCommands>
+        <PreCommands Condition="'$(OS)' != 'Windows_NT'">export &quot;HELIX_XUNIT_ARGS=-class %(Identity)&quot;</PreCommands>
         <Command>$(HelixCommand)</Command>
         <Timeout>$(_workItemTimeout)</Timeout>
       </HelixWorkItem>
index 3e668a3..d3dc572 100644 (file)
@@ -6,7 +6,7 @@
     <Error Text="%24(SdkWithWorkloadForTestingPath) is not set" Condition="'$(SdkWithWorkloadForTestingPath)' == ''" />
     <Error Text="%24(SdkVersionForWorkloadTesting) is not set" Condition="'$(SdkVersionForWorkloadTesting)' == ''" />
 
-    <Message Text="** Installing sdk $(SdkWithWorkloadForTestingPath) for workload based tests into $(SdkWithWorkloadForTestingPath)" Importance="High" />
+    <Message Text="** Installing sdk $(SdkVersionForWorkloadTesting) for workload based tests into $(SdkWithWorkloadForTestingPath)" Importance="High" />
 
     <RemoveDir Directories="$(SdkWithWorkloadForTestingPath)" />
     <MakeDir Directories="$(SdkWithWorkloadForTestingPath)" />
@@ -16,6 +16,8 @@
     </ItemGroup>
 
     <Copy SourceFiles="@(_SourceFiles)" DestinationFolder="$(SdkWithWorkloadForTestingPath)\%(_SourceFiles.RecursiveDir)" />
+    <Copy SourceFiles="$(MonoProjectRoot)\wasm\BlazorOverwrite.targets" DestinationFiles="$(SdkWithWorkloadForTestingPath)\sdk\$(SdkVersionForWorkloadTesting)\Sdks\Microsoft.NET.Sdk.BlazorWebAssembly\targets\Microsoft.NET.Sdk.BlazorWebAssembly.6_0.targets" />
+
     <WriteLinesToFile File="$(SdkWithWorkloadStampPath)" Lines="" Overwrite="true" />
   </Target>
 
@@ -23,7 +25,7 @@
     <Error Text="%24(SdkWithNoWorkloadForTestingPath) is not set" Condition="'$(SdkWithNoWorkloadForTestingPath)' == ''" />
     <Error Text="%24(SdkVersionForWorkloadTesting) is not set" Condition="'$(SdkVersionForWorkloadTesting)' == ''" />
 
-    <Message Text="** Installing sdk $(SdkWithNoWorkloadForTestingPath) for workload based tests into $(SdkWithNoWorkloadForTestingPath)" Importance="High" />
+    <Message Text="** Installing sdk $(SdkVersionForWorkloadTesting) for workload based tests into $(SdkWithNoWorkloadForTestingPath)" Importance="High" />
 
     <RemoveDir Directories="$(SdkWithNoWorkloadForTestingPath)" />
     <MakeDir Directories="$(SdkWithNoWorkloadForTestingPath)" />
@@ -39,6 +41,7 @@
     <Exec Condition="$([MSBuild]::IsOSPlatform('windows'))"
           Command='powershell -ExecutionPolicy ByPass -NoProfile -command "&amp; $(_DotNetInstallScriptPath) -InstallDir $(SdkWithNoWorkloadForTestingPath) -Version $(SdkVersionForWorkloadTesting)"' />
 
+    <Copy SourceFiles="$(MonoProjectRoot)\wasm\BlazorOverwrite.targets" DestinationFiles="$(SdkWithNoWorkloadForTestingPath)\sdk\$(SdkVersionForWorkloadTesting)\Sdks\Microsoft.NET.Sdk.BlazorWebAssembly\targets\Microsoft.NET.Sdk.BlazorWebAssembly.6_0.targets" />
     <WriteLinesToFile File="$(SdkWithNoWorkloadStampPath)" Lines="" Overwrite="true" />
   </Target>
 
index 1abf765..a5129bd 100644 (file)
@@ -72,6 +72,7 @@
         AotModulesTablePath="$(_AotModulesTablePath)"
         ToolPrefix="$(_AotToolPrefix)"
         LibraryFormat="$(_AotLibraryFormat)"
+        IntermediateOutputPath="$(IntermediateOutputPath)"
         UseLLVM="$(UseLLVM)"
         LLVMPath="$(MonoAotCrossDir)">
         <Output TaskParameter="CompiledAssemblies" ItemName="BundleAssemblies" />
index 8c3b217..64ffef7 100644 (file)
@@ -60,6 +60,7 @@
         AotModulesTablePath="$(AppDir)\modules.m"
         AotModulesTableLanguage="ObjC"
         OutputDir="$(PublishDir)"
+        IntermediateOutputPath="$(IntermediateOutputPath)"
         UseLLVM="$(UseLLVM)"
         LLVMPath="$(MonoAotCrossDir)">
         <Output TaskParameter="CompiledAssemblies" ItemName="BundleAssemblies" />
index 322a01b..c150529 100644 (file)
@@ -31,7 +31,6 @@
 
   <Target Name="PrepareDeltasForWasmApp" DependsOnTargets="Build;CompileDiff;ComputeDeltaFileOutputNames">
     <ItemGroup>
-      <WasmAssembliesToBundle Include="$(TargetDir)publish\*.dll" />
       <WasmFilesToIncludeInFileSystem Include="@(_DeltaFileForPublish)">
         <TargetPath>\%(_DeltaFileForPublish.Filename)%(_DeltaFileForPublish.Extension)</TargetPath>
       </WasmFilesToIncludeInFileSystem>
index 492c61c..bc7f6b2 100644 (file)
@@ -1,10 +1,5 @@
 <Project>
   <Import Project="../Directory.Build.targets" />
-  <Target Name="PrepareForWasmBuild" BeforeTargets="WasmBuildApp">
-    <ItemGroup>
-      <WasmAssembliesToBundle Include="$(TargetDir)publish\*.dll" />
-    </ItemGroup>
-  </Target>
   <Import Project="$(MonoProjectRoot)\wasm\build\WasmApp.InTree.targets" />
 
   <Target Name="BuildSampleInTree"
index 6af1fa6..d5ea012 100644 (file)
@@ -15,4 +15,4 @@ public class Test
         }
         return args.Length;
     }
-}
\ No newline at end of file
+}
index 2eb4adc..e3405db 100644 (file)
@@ -10,9 +10,12 @@ CONFIG?=Release
 
 WASM_DEFAULT_BUILD_ARGS?=/p:TargetArchitecture=wasm /p:TargetOS=Browser /p:Configuration=$(CONFIG)
 
-all: build
+all: publish
 
 build:
+       EMSDK_PATH=$(realpath $(TOP)/src/mono/wasm/emsdk) $(DOTNET) build $(DOTNET_Q_ARGS) $(WASM_DEFAULT_BUILD_ARGS) $(MSBUILD_ARGS) $(PROJECT_NAME)
+
+publish:
        EMSDK_PATH=$(realpath $(TOP)/src/mono/wasm/emsdk) $(DOTNET) publish $(DOTNET_Q_ARGS) $(WASM_DEFAULT_BUILD_ARGS) $(MSBUILD_ARGS) $(PROJECT_NAME)
 
 clean:
diff --git a/src/mono/wasm/BlazorOverwrite.targets b/src/mono/wasm/BlazorOverwrite.targets
new file mode 100644 (file)
index 0000000..a276d38
--- /dev/null
@@ -0,0 +1,741 @@
+<!--
+***********************************************************************************************
+Microsoft.NET.Sdk.BlazorWebAssembly.targets
+
+WARNING:  DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
+          created a backup copy.  Incorrect changes to this file will make it
+          impossible to load or build your projects from the command-line or the IDE.
+
+Copyright (c) .NET Foundation. All rights reserved.
+***********************************************************************************************
+-->
+<Project ToolsVersion="14.0">
+
+  <PropertyGroup>
+    <EnableDefaultContentItems Condition=" '$(EnableDefaultContentItems)' == '' ">true</EnableDefaultContentItems>
+
+    <!-- Trimmer defaults that depend on user-definable settings.
+        This must be configured before it's initialized in the .NET SDK targets (which are imported by the Razor SDK). -->
+    <SuppressTrimAnalysisWarnings Condition="'$(SuppressTrimAnalysisWarnings)' == '' And '$(TrimmerDefaultAction)' != 'link'">true</SuppressTrimAnalysisWarnings>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <!-- Paths to tools, tasks, and extensions are calculated relative to the BlazorWebAssemblySdkDirectoryRoot. This can be modified to test a local build. -->
+    <BlazorWebAssemblySdkDirectoryRoot Condition="'$(BlazorWebAssemblySdkDirectoryRoot)'==''">$(MSBuildThisFileDirectory)..\</BlazorWebAssemblySdkDirectoryRoot>
+    <_BlazorWebAssemblySdkTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">net6.0</_BlazorWebAssemblySdkTasksTFM>
+    <_BlazorWebAssemblySdkTasksTFM Condition=" '$(MSBuildRuntimeType)' != 'Core'">net472</_BlazorWebAssemblySdkTasksTFM>
+    <_BlazorWebAssemblySdkTasksAssembly>$(BlazorWebAssemblySdkDirectoryRoot)tools\$(_BlazorWebAssemblySdkTasksTFM)\Microsoft.NET.Sdk.BlazorWebAssembly.Tasks.dll</_BlazorWebAssemblySdkTasksAssembly>
+    <_BlazorWebAssemblySdkToolAssembly>$(BlazorWebAssemblySdkDirectoryRoot)tools\net6.0\Microsoft.NET.Sdk.BlazorWebAssembly.Tool.dll</_BlazorWebAssemblySdkToolAssembly>
+  </PropertyGroup>
+
+  <UsingTask TaskName="Microsoft.NET.Sdk.BlazorWebAssembly.GenerateBlazorWebAssemblyBootJson" AssemblyFile="$(_BlazorWebAssemblySdkTasksAssembly)" />
+  <UsingTask TaskName="Microsoft.NET.Sdk.BlazorWebAssembly.BlazorWriteSatelliteAssemblyFile" AssemblyFile="$(_BlazorWebAssemblySdkTasksAssembly)" />
+  <UsingTask TaskName="Microsoft.NET.Sdk.BlazorWebAssembly.BlazorReadSatelliteAssemblyFile" AssemblyFile="$(_BlazorWebAssemblySdkTasksAssembly)" />
+  <UsingTask TaskName="Microsoft.NET.Sdk.BlazorWebAssembly.BrotliCompress" AssemblyFile="$(_BlazorWebAssemblySdkTasksAssembly)" />
+  <UsingTask TaskName="Microsoft.NET.Sdk.BlazorWebAssembly.GzipCompress" AssemblyFile="$(_BlazorWebAssemblySdkTasksAssembly)" />
+  <UsingTask TaskName="Microsoft.NET.Sdk.BlazorWebAssembly.CreateBlazorTrimmerRootDescriptorFile" AssemblyFile="$(_BlazorWebAssemblySdkTasksAssembly)" />
+  <UsingTask TaskName="Microsoft.NET.Sdk.BlazorWebAssembly.ComputeBlazorFilesToCompress" AssemblyFile="$(_BlazorWebAssemblySdkTasksAssembly)" />
+  <UsingTask TaskName="Microsoft.NET.Sdk.BlazorWebAssembly.ComputeBlazorBuildAssets" AssemblyFile="$(_BlazorWebAssemblySdkTasksAssembly)" />
+  <UsingTask TaskName="Microsoft.NET.Sdk.BlazorWebAssembly.ComputeBlazorPublishAssets" AssemblyFile="$(_BlazorWebAssemblySdkTasksAssembly)" />
+
+  <PropertyGroup>
+    <SelfContained>true</SelfContained>
+    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
+
+    <!-- Runtime feature defaults to trim unnecessary code -->
+    <InvariantGlobalization Condition="'$(InvariantGlobalization)' == ''">false</InvariantGlobalization>
+    <EventSourceSupport Condition="'$(EventSourceSupport)' == ''">false</EventSourceSupport>
+    <UseSystemResourceKeys Condition="'$(UseSystemResourceKeys)' == ''">true</UseSystemResourceKeys>
+    <EnableUnsafeUTF7Encoding Condition="'$(EnableUnsafeUTF7Encoding)' == ''">false</EnableUnsafeUTF7Encoding>
+    <HttpActivityPropagationSupport Condition="'$(HttpActivityPropagationSupport)' == ''">false</HttpActivityPropagationSupport>
+    <NullabilityInfoContextSupport Condition="'$(NullabilityInfoContextSupport)' == ''">false</NullabilityInfoContextSupport>
+    <_AggressiveAttributeTrimming Condition="'$(_AggressiveAttributeTrimming)' == ''">true</_AggressiveAttributeTrimming>
+    <DebuggerSupport Condition="'$(DebuggerSupport)' == '' and '$(Configuration)' != 'Debug'">false</DebuggerSupport>
+    <BlazorCacheBootResources Condition="'$(BlazorCacheBootResources)' == ''">true</BlazorCacheBootResources>
+
+    <!-- Turn off parts of the build that do not apply to WASM projects -->
+    <GenerateDependencyFile>false</GenerateDependencyFile>
+    <GenerateRuntimeConfigurationFiles>false</GenerateRuntimeConfigurationFiles>
+    <PreserveCompilationContext>false</PreserveCompilationContext>
+    <PreserveCompilationReferences>false</PreserveCompilationReferences>
+    <IsWebConfigTransformDisabled>true</IsWebConfigTransformDisabled>
+
+    <!-- Don't generate a NETSDK1151 error if a non self-contained Exe references a Blazor Exe -->
+    <ShouldBeValidatedAsExecutableReference>false</ShouldBeValidatedAsExecutableReference>
+
+    <_TargetingNET60OrLater Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '6.0'))">true</_TargetingNET60OrLater>
+
+    <!-- JS Modules -->
+    <!-- We disable the manifest generation because we are going to inline the modules in the blazor.boot.json manifest -->
+    <GenerateJSModuleManifest>false</GenerateJSModuleManifest>
+
+    <DisableAutoWasmBuildApp>true</DisableAutoWasmBuildApp>
+    <DisableAutoWasmPublishApp>true</DisableAutoWasmPublishApp>
+    <EnableDefaultWasmAssembliesToBundle>false</EnableDefaultWasmAssembliesToBundle>
+    <WasmNestedPublishAppDependsOn>ComputeFilesToPublish;_GatherWasmFilesToPublish;$(WasmNestedPublishAppDependsOn)</WasmNestedPublishAppDependsOn>
+    <_ScrambleDotnetJsFileNameAfterThisTarget Condition="'$(UsingBrowserRuntimeWorkload)' != 'true'">ResolveRuntimePackAssets</_ScrambleDotnetJsFileNameAfterThisTarget>
+    <_ScrambleDotnetJsFileNameAfterThisTarget Condition="'$(UsingBrowserRuntimeWorkload)' == 'true'">WasmBuildApp</_ScrambleDotnetJsFileNameAfterThisTarget>
+
+    <!-- Workaround https://github.com/dotnet/sdk/issues/20923 where we end up with two publish dirs - 'Publish', and 'publish' -->
+    <PublishDirName Condition="'$(PublishDirName)' == 'publish'">Publish</PublishDirName>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <!-- Configuration for the platform compatibility analyzer. See https://github.com/dotnet/designs/blob/main/accepted/2020/platform-exclusion/platform-exclusion.md#build-configuration-for-platforms -->
+    <SupportedPlatform Remove="@(SupportedPlatform)" />
+    <SupportedPlatform Include="browser" />
+  </ItemGroup>
+
+  <!-- Wire-up static web assets -->
+  <PropertyGroup>
+    <ResolveStaticWebAssetsInputsDependsOn>
+      $(ResolveStaticWebAssetsInputsDependsOn);
+      _AddBlazorWasmStaticWebAssets;
+    </ResolveStaticWebAssetsInputsDependsOn>
+
+    <StaticWebAssetsPrepareForRunDependsOn>
+      _GenerateBuildBlazorBootJson;
+      $(StaticWebAssetsPrepareForRunDependsOn)
+    </StaticWebAssetsPrepareForRunDependsOn>
+
+    <ResolvePublishStaticWebAssetsDependsOn>
+      $(ResolvePublishStaticWebAssetsDependsOn);
+      ProcessPublishFilesForBlazor;
+      ComputeBlazorExtensions;
+      _AddPublishBlazorBootJsonToStaticWebAssets;
+    </ResolvePublishStaticWebAssetsDependsOn>
+
+    <GenerateStaticWebAssetsPublishManifestDependsOn>
+      $(GenerateStaticWebAssetsPublishManifestDependsOn);
+      GeneratePublishBlazorBootJson;
+    </GenerateStaticWebAssetsPublishManifestDependsOn>
+
+  </PropertyGroup>
+
+  <Import Project="Microsoft.NET.Sdk.BlazorWebAssembly.ServiceWorkerAssetsManifest.targets" Condition="'$(ServiceWorkerAssetsManifest)' != ''" />
+
+  <Target Name="_ScrambleDotnetJsFileNameForPublish">
+    <!--
+      We want the dotnet.js file output to have a version to better work with caching. We'll append the runtime version to the file name as soon as file has been discovered.
+    -->
+    <PropertyGroup>
+      <_DotNetJsVersion>$(BundledNETCoreAppPackageVersion)</_DotNetJsVersion>
+      <_DotNetJsVersion Condition="'$(RuntimeFrameworkVersion)' != ''">$(RuntimeFrameworkVersion)</_DotNetJsVersion>
+      <_BlazorDotnetJsFileName>dotnet.$(_DotNetJsVersion).js</_BlazorDotnetJsFileName>
+    </PropertyGroup>
+
+    <ItemGroup Condition="@(WasmNativeAsset->Count()) != 0">
+      <_DotNetJsItem Remove="@(_DotNetJsItem)" />
+      <_DotNetJsItem Include="@(WasmNativeAsset)" Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'">
+        <RelativePath>_framework/$(_BlazorDotnetJsFileName)</RelativePath>
+        <AssetType>native</AssetType>
+        <CopyLocal>true</CopyLocal>
+      </_DotNetJsItem>
+    </ItemGroup>
+
+    <ItemGroup>
+      <_DotnetJsStaticWebAssetCandidate Remove="@(_DotnetJsStaticWebAssetCandidate)" />
+      <_DotnetJsCopyCandidates Remove="@(_DotnetJsCopyCandidates)" />
+    </ItemGroup>
+
+    <DefineStaticWebAssets Condition="'@(_DotNetJsItem->Count())' != '0'"
+      CandidateAssets="@(_DotNetJsItem)"
+      SourceId="$(PackageId)"
+      SourceType="Computed"
+      AssetKind="Build"
+      AssetRole="Primary"
+      AssetTraitName="BlazorWebAssemblyResource"
+      AssetTraitValue="native"
+      CopyToOutputDirectory="PreserveNewest"
+      CopyToPublishDirectory="Never"
+      ContentRoot="$(IntermediateOutputPath)\blazor"
+      BasePath="$(StaticWebAssetBasePath)"
+    >
+      <Output TaskParameter="Assets" ItemName="_DotnetJsStaticWebAssetCandidate" />
+      <Output TaskParameter="CopyCandidates" ItemName="_DotnetJsCopyCandidates" />
+    </DefineStaticWebAssets>
+
+    <Copy
+      SourceFiles="@(_DotnetJsCopyCandidates)"
+      DestinationFiles="@(_DotnetJsCopyCandidates->'%(TargetPath)')"
+      SkipUnchangedFiles="true"
+      OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" />
+
+    <ItemGroup Condition="'@(_DotNetJsItem->Count())' != '0'">
+      <ReferenceCopyLocalPaths Remove="@(_DotNetJsItem)" />
+      <_DotnetJsStaticWebAsset Include="@(_DotnetJsStaticWebAssetCandidate->'%(ContentRoot)_framework\dotnet.js')" />
+      <_BlazorStaticWebAsset Include="@(_DotnetJsStaticWebAsset)" />
+    </ItemGroup>
+  </Target>
+
+  <Target Name="_ScrambleDotnetJsFileName" AfterTargets="$(_ScrambleDotnetJsFileNameAfterThisTarget)">
+    <!--
+      We want the dotnet.js file output to have a version to better work with caching. We'll append the runtime version to the file name as soon as file has been discovered.
+    -->
+    <PropertyGroup>
+      <_DotNetJsVersion>$(BundledNETCoreAppPackageVersion)</_DotNetJsVersion>
+      <_DotNetJsVersion Condition="'$(RuntimeFrameworkVersion)' != ''">$(RuntimeFrameworkVersion)</_DotNetJsVersion>
+      <_BlazorDotnetJsFileName>dotnet.$(_DotNetJsVersion).js</_BlazorDotnetJsFileName>
+    </PropertyGroup>
+
+    <ItemGroup Condition="@(WasmNativeAsset->Count()) == 0">
+      <_DotNetJsItem Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.DestinationSubPath)' == 'dotnet.js' AND '%(ReferenceCopyLocalPaths.AssetType)' == 'native'">
+        <RelativePath>_framework/$(_BlazorDotnetJsFileName)</RelativePath>
+      </_DotNetJsItem>
+    </ItemGroup>
+
+    <ItemGroup Condition="@(WasmNativeAsset->Count()) != 0">
+      <_DotNetJsItem Remove="@(_DotNetJsItem)" />
+      <_DotNetJsItem Include="@(WasmNativeAsset)" Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'">
+        <RelativePath>_framework/$(_BlazorDotnetJsFileName)</RelativePath>
+        <AssetType>native</AssetType>
+        <CopyLocal>true</CopyLocal>
+      </_DotNetJsItem>
+    </ItemGroup>
+
+    <DefineStaticWebAssets Condition="'@(_DotNetJsItem->Count())' != '0'"
+      CandidateAssets="@(_DotNetJsItem)"
+      SourceId="$(PackageId)"
+      SourceType="Computed"
+      AssetKind="Build"
+      AssetRole="Primary"
+      AssetTraitName="BlazorWebAssemblyResource"
+      AssetTraitValue="native"
+      CopyToOutputDirectory="PreserveNewest"
+      CopyToPublishDirectory="Never"
+      ContentRoot="$(IntermediateOutputPath)\blazor"
+      BasePath="$(StaticWebAssetBasePath)"
+    >
+      <Output TaskParameter="Assets" ItemName="_DotnetJsStaticWebAssetCandidate" />
+      <Output TaskParameter="CopyCandidates" ItemName="_DotnetJsCopyCandidates" />
+    </DefineStaticWebAssets>
+
+    <Copy
+      SourceFiles="@(_DotnetJsCopyCandidates)"
+      DestinationFiles="@(_DotnetJsCopyCandidates->'%(TargetPath)')"
+      SkipUnchangedFiles="true"
+      OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" />
+
+    <ItemGroup Condition="'@(_DotNetJsItem->Count())' != '0'">
+      <ReferenceCopyLocalPaths Remove="@(_DotNetJsItem)" />
+      <_DotnetJsStaticWebAsset Include="@(_DotnetJsStaticWebAssetCandidate->'%(ContentRoot)_framework\dotnet.js')" />
+      <_BlazorStaticWebAsset Include="@(_DotnetJsStaticWebAsset)" />
+    </ItemGroup>
+  </Target>
+
+  <Target Name="_BlazorWasmNativeForBuild" DependsOnTargets="_GatherWasmFilesToBuild;WasmBuildApp" Condition="'$(UsingBrowserRuntimeWorkload)' == 'true'" />
+
+  <Target Name="_GatherWasmFilesToBuild">
+    <ItemGroup>
+      <WasmAssembliesToBundle Remove="@(WasmAssembliesToBundle)" />
+      <WasmAssembliesToBundle Include="@(ReferenceCopyLocalPaths);@(MainAssembly)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'" />
+      <WasmAssembliesToBundle Condition="'%(WasmAssembliesToBundle.FileName)' == 'Microsoft.JSInterop.WebAssembly'" AOT_InternalForceToInterpret="true" />
+    </ItemGroup>
+  </Target>
+
+  <Target Name="_ResolveBlazorWasmConfiguration">
+    <PropertyGroup>
+      <_BlazorEnableTimeZoneSupport>$(BlazorEnableTimeZoneSupport)</_BlazorEnableTimeZoneSupport>
+      <_BlazorEnableTimeZoneSupport Condition="'$(_BlazorEnableTimeZoneSupport)' == ''">true</_BlazorEnableTimeZoneSupport>
+      <_BlazorInvariantGlobalization>$(InvariantGlobalization)</_BlazorInvariantGlobalization>
+      <_BlazorInvariantGlobalization Condition="'$(_BlazorInvariantGlobalization)' == ''">true</_BlazorInvariantGlobalization>
+      <_BlazorCopyOutputSymbolsToOutputDirectory>$(CopyOutputSymbolsToOutputDirectory)</_BlazorCopyOutputSymbolsToOutputDirectory>
+      <_BlazorCopyOutputSymbolsToOutputDirectory Condition="'$(_BlazorCopyOutputSymbolsToOutputDirectory)'==''">true</_BlazorCopyOutputSymbolsToOutputDirectory>
+      <_BlazorWebAssemblyLoadAllGlobalizationData>$(BlazorWebAssemblyLoadAllGlobalizationData)</_BlazorWebAssemblyLoadAllGlobalizationData>
+      <_BlazorWebAssemblyLoadAllGlobalizationData Condition="'$(_BlazorWebAssemblyLoadAllGlobalizationData)' == ''">false</_BlazorWebAssemblyLoadAllGlobalizationData>
+
+      <!-- Workaround for https://github.com/dotnet/sdk/issues/12114-->
+      <PublishDir Condition="'$(AppendRuntimeIdentifierToOutputPath)' != 'true' AND '$(PublishDir)' == '$(OutputPath)$(RuntimeIdentifier)\$(PublishDirName)\'">$(OutputPath)$(PublishDirName)\</PublishDir>
+    </PropertyGroup>
+  </Target>
+
+  <Target Name="_ResolveBlazorWasmOutputs" DependsOnTargets="ResolveReferences;PrepareResourceNames;ComputeIntermediateSatelliteAssemblies;_ResolveBlazorWasmConfiguration;_BlazorWasmNativeForBuild" BeforeTargets="_RazorPrepareForRun">
+    <ItemGroup>
+      <_BlazorJSFile Include="$(BlazorWebAssemblyJSPath)" />
+      <_BlazorJSFile Include="$(BlazorWebAssemblyJSMapPath)" Condition="Exists('$(BlazorWebAssemblyJSMapPath)')" />
+      <_BlazorJsFile>
+        <RelativePath>_framework/%(Filename)%(Extension)</RelativePath>
+      </_BlazorJsFile>
+
+      <!-- A missing blazor.webassembly.js is our packaging error. Produce an error so it's discovered early. -->
+      <Error
+        Text="Unable to find BlazorWebAssembly JS files. This usually indicates a packaging error."
+        Code="RAZORSDK1007"
+        Condition="'@(_BlazorJSFile->Count())' == '0'" />
+
+      <_BlazorConfigFileCandidates Include="@(StaticWebAsset)" Condition="'%(SourceType)' == 'Discovered'" />
+
+      <ReferenceCopyLocalPaths Remove="@(ReferenceCopyLocalPaths)"
+                               Condition="@(WasmNativeAsset->Count()) > 0 and '%(FileName)' == 'dotnet' and ('%(Extension)' == '.wasm' or '%(Extension)' == '.js')" />
+    </ItemGroup>
+
+    <ComputeBlazorBuildAssets
+      Candidates="@(ReferenceCopyLocalPaths);@(WasmNativeAsset)"
+      ProjectAssembly="@(IntermediateAssembly)"
+      ProjectDebugSymbols="@(_DebugSymbolsIntermediatePath)"
+      SatelliteAssemblies="@(ReferenceSatellitePaths)"
+      ProjectSatelliteAssemblies="@(IntermediateSatelliteAssembliesWithTargetPath)"
+      TimeZoneSupport="$(_BlazorEnableTimeZoneSupport)"
+      InvariantGlobalization="$(_BlazorInvariantGlobalization)"
+      CopySymbols="$(_BlazorCopyOutputSymbolsToOutputDirectory)"
+      OutputPath="$(OutputPath)"
+    >
+      <Output TaskParameter="AssetCandidates" ItemName="_BuildAssetsCandidates" />
+      <Output TaskParameter="FilesToRemove" ItemName="_BlazorBuildFilesToRemove" />
+    </ComputeBlazorBuildAssets>
+
+    <DefineStaticWebAssets
+      CandidateAssets="@(_BuildAssetsCandidates)"
+      SourceId="$(PackageId)"
+      SourceType="Computed"
+      AssetKind="Build"
+      AssetRole="Primary"
+      CopyToOutputDirectory="PreserveNewest"
+      CopyToPublishDirectory="Never"
+      ContentRoot="$(OutputPath)wwwroot"
+      BasePath="$(StaticWebAssetBasePath)"
+    >
+      <Output TaskParameter="Assets" ItemName="_BlazorStaticWebAsset" />
+    </DefineStaticWebAssets>
+
+    <DefineStaticWebAssets
+      CandidateAssets="@(_BlazorJSFile)"
+      SourceId="$(PackageId)"
+      SourceType="Computed"
+      AssetKind="All"
+      AssetRole="Primary"
+      AssetTraitName="BlazorWebAssemblyResource"
+      AssetTraitValue="boot"
+      CopyToOutputDirectory="PreserveNewest"
+      CopyToPublishDirectory="PreserveNewest"
+      ContentRoot="$(OutputPath)wwwroot"
+      BasePath="$(StaticWebAssetBasePath)"
+    >
+      <Output TaskParameter="Assets" ItemName="_BlazorStaticWebAsset" />
+    </DefineStaticWebAssets>
+
+    <DefineStaticWebAssets
+      CandidateAssets="@(_BlazorConfigFileCandidates)"
+      AssetTraitName="BlazorWebAssemblyResource"
+      AssetTraitValue="settings"
+      RelativePathFilter="appsettings*.json"
+    >
+      <Output TaskParameter="Assets" ItemName="_BlazorJsConfigStaticWebAsset" />
+    </DefineStaticWebAssets>
+
+    <ItemGroup>
+      <!-- Update the config blazor static web asset since we've given it a trait -->
+      <StaticWebAsset Remove="@(_BlazorJsConfigStaticWebAsset)" />
+      <StaticWebAsset Include="@(_BlazorJsConfigStaticWebAsset)" />
+
+      <ReferenceCopyLocalPaths Remove="@(_BlazorBuildFilesToRemove)" />
+    </ItemGroup>
+
+    <PropertyGroup>
+      <_BlazorBuildGZipCompressDirectory>$(IntermediateOutputPath)build-gz\</_BlazorBuildGZipCompressDirectory>
+    </PropertyGroup>
+
+    <!--
+      Compress referenced binaries using GZip during build. This skips files such as the project's assemblies
+      that change from build to build. Runtime assets contribute to the bulk of the download size. Compressing it
+      has the most benefit while avoiding any ongoing costs to the dev inner loop.
+    -->
+
+    <ComputeBlazorFilesToCompress Assets="@(_BlazorStaticWebAsset)">
+      <Output TaskParameter="AssetsToCompress" ItemName="_GzipFileToCompressForBuild" />
+    </ComputeBlazorFilesToCompress>
+
+    <GZipCompress
+      FilesToCompress="@(_GzipFileToCompressForBuild)"
+      OutputDirectory="$(_BlazorBuildGZipCompressDirectory)">
+
+      <Output TaskParameter="CompressedFiles" ItemName="_BlazorBuildGZipCompressedFile" />
+      <Output TaskParameter="CompressedFiles" ItemName="FileWrites" />
+    </GZipCompress>
+
+    <ItemGroup>
+      <_BlazorBuildGZipCompressedFile>
+        <OriginalItemSpec>%(RelatedAsset)</OriginalItemSpec>
+      </_BlazorBuildGZipCompressedFile>
+
+      <_BlazorGzipStaticWebAsset Include="@(_BlazorBuildGZipCompressedFile->'%(FullPath)')" />
+      </ItemGroup>
+
+    <PropertyGroup>
+      <_BlazorBuildBootJsonPath>$(IntermediateOutputPath)blazor.boot.json</_BlazorBuildBootJsonPath>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <_BuildBlazorBootJson
+        Include="$(_BlazorBuildBootJsonPath)"
+        RelativePath="_framework/blazor.boot.json" />
+    </ItemGroup>
+
+    <DefineStaticWebAssets
+      CandidateAssets="@(_BuildBlazorBootJson)"
+      SourceId="$(PackageId)"
+      SourceType="Computed"
+      AssetKind="Build"
+      AssetRole="Primary"
+      AssetTraitName="BlazorWebAssemblyResource"
+      AssetTraitValue="manifest"
+      CopyToOutputDirectory="PreserveNewest"
+      CopyToPublishDirectory="Never"
+      ContentRoot="$(OutDir)wwwroot"
+      BasePath="$(StaticWebAssetBasePath)"
+    >
+      <Output TaskParameter="Assets" ItemName="_BuildBlazorBootJsonStaticWebAsset" />
+  </DefineStaticWebAssets>
+
+  </Target>
+
+  <Target Name="_AddBlazorWasmStaticWebAssets" DependsOnTargets="_ResolveBlazorWasmOutputs">
+    <ItemGroup>
+      <StaticWebAsset Include="@(_BlazorStaticWebAsset)" />
+      <StaticWebAsset Include="@(_BlazorGzipStaticWebAsset)" />
+      <StaticWebAsset Include="@(_BuildBlazorBootJsonStaticWebAsset)" />
+    </ItemGroup>
+
+  </Target>
+
+  <Target Name="_GenerateBuildBlazorBootJson" DependsOnTargets="ResolveStaticWebAssetsInputs">
+    <PropertyGroup>
+      <_BlazorBuildBootJsonPath>$(IntermediateOutputPath)blazor.boot.json</_BlazorBuildBootJsonPath>
+      <_BlazorWebAssemblyLoadAllGlobalizationData Condition="'$(BlazorWebAssemblyLoadAllGlobalizationData)' == ''">false</_BlazorWebAssemblyLoadAllGlobalizationData>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <_BlazorJsModuleCandidatesForBuild
+        Include="@(StaticWebAsset)"
+        Condition="'%(StaticWebAsset.AssetTraitName)' == 'JSModule' and '%(StaticWebAsset.AssetTraitValue)' == 'JSLibraryModule' and '%(AssetKind)' != 'Publish'" />
+    </ItemGroup>
+
+    <GetFileHash Files="@(_BlazorStaticWebAsset->'%(OriginalItemSpec)')" Algorithm="SHA256" HashEncoding="base64">
+      <Output TaskParameter="Items" ItemName="_BlazorOutputWithHash" />
+    </GetFileHash>
+
+    <ComputeStaticWebAssetsTargetPaths
+      Assets="@(_BlazorJsModuleCandidatesForBuild)"
+      PathPrefix=""
+      UseAlternatePathDirectorySeparator="true"
+    >
+      <Output TaskParameter="AssetsWithTargetPath" ItemName="_BlazorJsModuleCandidatesForBuildWithTargetPath" />
+    </ComputeStaticWebAssetsTargetPaths>
+
+    <GetFileHash Files="@(_BlazorJsModuleCandidatesForBuildWithTargetPath)" Algorithm="SHA256" HashEncoding="base64">
+      <Output TaskParameter="Items" ItemName="_BlazorOutputWithHash" />
+    </GetFileHash>
+
+
+    <GenerateBlazorWebAssemblyBootJson
+      AssemblyPath="@(IntermediateAssembly)"
+      Resources="@(_BlazorOutputWithHash)"
+      DebugBuild="true"
+      LinkerEnabled="false"
+      CacheBootResources="$(BlazorCacheBootResources)"
+      OutputPath="$(_BlazorBuildBootJsonPath)"
+      ConfigurationFiles="@(_BlazorJsConfigStaticWebAsset)"
+      LazyLoadedAssemblies="@(BlazorWebAssemblyLazyLoad)"
+      InvariantGlobalization="$(InvariantGlobalization)"
+      LoadAllICUData="$(_BlazorWebAssemblyLoadAllGlobalizationData)" />
+
+    <ItemGroup>
+      <FileWrites Include="$(_BlazorBuildBootJsonPath)" />
+    </ItemGroup>
+
+  </Target>
+
+  <!-- Just print a message here, static web assets takes care of all the copying -->
+  <Target Name="_BlazorCopyFilesToOutputDirectory" AfterTargets="CopyFilesToOutputDirectory">
+    <Message Importance="High" Text="$(MSBuildProjectName) (Blazor output) -&gt; $(TargetDir)wwwroot" Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)'!='true'" />
+  </Target>
+
+  <!-- Publish starts here -->
+
+  <!-- Make sure that ResolveAssemblyReferences runs early enough to ensure satellite assemblies are populated in the ResolvedFilesToPublish -->
+  <Target Name="_BlazorPrepareForPublish"
+    DependsOnTargets="PrepareResourceNames;ComputeIntermediateSatelliteAssemblies;ResolveAssemblyReferences"
+    BeforeTargets="PrepareForPublish" />
+
+  <!--
+    This target configures special trimming for Microsoft.Extensions.* and Microsoft.AspNetCore.* assemblies.
+    We only need this for net5.0 projects since trimmablity is declared using assembly attributes in net6.0 and later.
+  -->
+  <Target Name="_BlazorWasmPrepareForLink" BeforeTargets="PrepareForILLink" Condition="'$(_TargetingNET60OrLater)' != 'true'">
+    <PropertyGroup>
+      <_BlazorTypeGranularTrimmerDescriptorFile>$(IntermediateOutputPath)typegranularity.trimmerdescriptor.xml</_BlazorTypeGranularTrimmerDescriptorFile>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <_BlazorTypeGranularAssembly
+          Include="@(ManagedAssemblyToLink)"
+          Condition="'%(Extension)' == '.dll' AND $([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))">
+        <Required>false</Required>
+        <Preserve>all</Preserve>
+      </_BlazorTypeGranularAssembly>
+
+      <ManagedAssemblyToLink
+        IsTrimmable="true"
+        Condition="'%(Extension)' == '.dll' AND ($([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.')) or $([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.')))" />
+    </ItemGroup>
+
+    <CreateBlazorTrimmerRootDescriptorFile
+      Assemblies="@(_BlazorTypeGranularAssembly)"
+      TrimmerFile="$(_BlazorTypeGranularTrimmerDescriptorFile)" />
+
+    <ItemGroup>
+      <TrimmerRootDescriptor Include="$(_BlazorTypeGranularTrimmerDescriptorFile)" />
+
+      <FileWrites Include="$(_BlazorTypeGranularTrimmerDescriptorFile)" />
+    </ItemGroup>
+  </Target>
+
+  <Target Name="ProcessPublishFilesForBlazor" DependsOnTargets="_ResolveBlazorWasmConfiguration;LoadStaticWebAssetsBuildManifest" AfterTargets="ILLink" Condition="'$(WasmBuildingForNestedPublish)' != 'true'">
+    <PropertyGroup>
+      <_BlazorAotEnabled>$(UsingBrowserRuntimeWorkload)</_BlazorAotEnabled>
+      <_BlazorAotEnabled Condition="'$(_BlazorAotEnabled)' == ''">false</_BlazorAotEnabled>
+      <_BlazorLinkerEnabled>$(PublishTrimmed)</_BlazorLinkerEnabled>
+      <_BlazorLinkerEnabled Condition="'$(_BlazorLinkerEnabled)' == ''">true</_BlazorLinkerEnabled>
+    </PropertyGroup>
+
+    <!-- The list of static web assets already contains all the assets from the build. We want to correct certain assets that might
+         have changed as part of the publish process. We are going to do so as follows:
+         * We will update Blazor runtime asset dlls if we are running PublishTrimmed
+         * We will update Blazor native runtime resources if we are using Aot
+         Other than that, we'll filter the unwanted assets from the list of resolved files to publish in the same way we did during the build.
+    -->
+
+    <ItemGroup>
+      <_BlazorPublishPrefilteredAssets
+        Include="@(StaticWebAsset)"
+        Condition="'%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' or '%(StaticWebAsset.AssetTraitName)' == 'Culture' or '%(AssetRole)' == 'Alternative'" />
+    </ItemGroup>
+
+    <ComputeBlazorPublishAssets
+      ResolvedFilesToPublish="@(ResolvedFileToPublish)"
+      TimeZoneSupport="$(_BlazorEnableTimeZoneSupport)"
+      PublishPath="$(PublishDir)"
+      WasmAotAssets="@(WasmNativeAsset)"
+      InvariantGlobalization="$(_BlazorInvariantGlobalization)"
+      CopySymbols="$(CopyOutputSymbolsToPublishDirectory)"
+      ExistingAssets="@(_BlazorPublishPrefilteredAssets)"
+    >
+      <Output TaskParameter="NewCandidates" ItemName="_NewBlazorPublishStaticWebAssets" />
+      <Output TaskParameter="FilesToRemove" ItemName="_PublishResolvedFilesToRemove" />
+    </ComputeBlazorPublishAssets>
+
+    <ItemGroup>
+      <ResolvedFileToPublish Remove="@(_PublishResolvedFilesToRemove)" />
+      <StaticWebAsset Include="@(_NewBlazorPublishStaticWebAssets)" />
+      <PublishBlazorBootStaticWebAsset 
+        Include="@(StaticWebAsset)"
+        Condition="'%(AssetKind)' != 'Build' and 
+                    (('%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' and '%(StaticWebAsset.AssetTraitValue)' != 'manifest' and '%(StaticWebAsset.AssetTraitValue)' != 'boot') or
+                    '%(StaticWebAsset.AssetTraitName)' == 'Culture')" />
+    </ItemGroup>
+  </Target>
+
+  <Target 
+    Name="ComputeBlazorExtensions"
+    AfterTargets="ProcessPublishFilesForBlazor"
+    DependsOnTargets="$(ComputeBlazorExtensionsDependsOn)" >
+    <ItemGroup>
+      <_BlazorExtensionsCandidate Include="@(BlazorPublishExtension->'%(FullPath)')">
+        <SourceId>$(PackageId)</SourceId>
+        <SourceType>Computed</SourceType>
+        <ContentRoot>$(PublishDir)wwwroot</ContentRoot>
+        <BasePath>$(StaticWebAssetBasePath)</BasePath>
+        <RelativePath>%(BlazorPublishExtension.RelativePath)</RelativePath>
+        <AssetKind>Publish</AssetKind>
+        <AssetMode>All</AssetMode>
+        <AssetRole>Primary</AssetRole>
+        <AssetTraitName>BlazorWebAssemblyResource</AssetTraitName>
+        <AssetTraitValue>extension:%(BlazorPublishExtension.ExtensionName)</AssetTraitValue>
+        <CopyToOutputDirectory>Never</CopyToOutputDirectory>
+        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
+        <OriginalItemSpec>%(BlazorPublishExtension.Identity)</OriginalItemSpec>
+      </_BlazorExtensionsCandidate>
+    </ItemGroup>
+
+    <DefineStaticWebAssets CandidateAssets="@(_BlazorExtensionsCandidate)">
+      <Output TaskParameter="Assets" ItemName="StaticWebAsset" />
+      <Output TaskParameter="Assets" ItemName="_BlazorExtensionsCandidatesForPublish" />
+    </DefineStaticWebAssets>
+
+  </Target>
+
+  <Target Name="_AddBlazorWebConfigFile" AfterTargets="ILLink">
+    <ItemGroup Condition="'@(ResolvedFileToPublish->AnyHaveMetadataValue('RelativePath', 'web.config'))' != 'true'">
+      <ResolvedFileToPublish
+         Include="$(MSBuildThisFileDirectory)BlazorWasm.web.config"
+         ExcludeFromSingleFile="true"
+         CopyToPublishDirectory="PreserveNewest"
+         RelativePath="web.config" />
+    </ItemGroup>
+  </Target>
+
+  <Target Name="_AddPublishBlazorBootJsonToStaticWebAssets">
+    <ItemGroup>
+      <_PublishBlazorBootJson
+        Include="$(IntermediateOutputPath)blazor.publish.boot.json"
+        RelativePath="_framework/blazor.boot.json" />
+    </ItemGroup>
+
+    <DefineStaticWebAssets
+      CandidateAssets="@(_PublishBlazorBootJson)"
+      SourceId="$(PackageId)"
+      SourceType="Computed"
+      AssetKind="Publish"
+      AssetRole="Primary"
+      AssetTraitName="BlazorWebAssemblyResource"
+      AssetTraitValue="manifest"
+      CopyToOutputDirectory="Never"
+      CopyToPublishDirectory="PreserveNewest"
+      ContentRoot="$(PublishDir)wwwroot"
+      BasePath="$(StaticWebAssetBasePath)"
+    >
+      <Output TaskParameter="Assets" ItemName="StaticWebAsset" />
+    </DefineStaticWebAssets>
+  </Target>
+
+  <Target Name="GeneratePublishBlazorBootJson">
+
+    <ItemGroup>
+      <_BlazorPublishAsset
+        Include="@(StaticWebAsset)"
+        Condition="'%(AssetKind)' != 'Build' and '%(StaticWebAsset.AssetTraitValue)' != 'manifest' and ('%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' or '%(StaticWebAsset.AssetTraitName)' == 'Culture') and '%(StaticWebAsset.AssetTraitValue)' != 'boot'" />
+
+      <_BlazorPublishConfigFile
+        Include="@(StaticWebAsset)"
+        Condition="'%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' and '%(StaticWebAsset.AssetTraitValue)' == 'settings'"/>
+
+      <_BlazorJsModuleCandidatesForPublish
+        Include="@(StaticWebAsset)"
+        Condition="'%(StaticWebAsset.AssetTraitName)' == 'JSModule' and '%(StaticWebAsset.AssetTraitValue)' == 'JSLibraryModule' and '%(AssetKind)' != 'Build'" />
+
+      <!-- We remove the extensions since they are added to the list of static web assets but we need to compute the target path for them -->
+      <_BlazorPublishAsset Remove="@(_BlazorExtensionsCandidatesForPublish)" />
+
+    </ItemGroup>
+
+    <ComputeStaticWebAssetsTargetPaths
+      Assets="@(_BlazorJsModuleCandidatesForPublish);@(_BlazorExtensionsCandidatesForPublish)"
+      PathPrefix=""
+      UseAlternatePathDirectorySeparator="true"
+    >
+      <Output TaskParameter="AssetsWithTargetPath" ItemName="_BlazorCandidatesForPublishWithTargetPath" />
+    </ComputeStaticWebAssetsTargetPaths>
+
+    <GetFileHash Files="@(_BlazorPublishAsset);@(_BlazorCandidatesForPublishWithTargetPath)" Algorithm="SHA256" HashEncoding="base64">
+      <Output TaskParameter="Items" ItemName="_BlazorPublishBootResourceWithHash" />
+    </GetFileHash>
+
+    <GenerateBlazorWebAssemblyBootJson
+      AssemblyPath="@(IntermediateAssembly)"
+      Resources="@(_BlazorPublishBootResourceWithHash)"
+      DebugBuild="false"
+      LinkerEnabled="$(PublishTrimmed)"
+      CacheBootResources="$(BlazorCacheBootResources)"
+      OutputPath="$(IntermediateOutputPath)blazor.publish.boot.json"
+      ConfigurationFiles="@(_BlazorPublishConfigFile)"
+      LazyLoadedAssemblies="@(BlazorWebAssemblyLazyLoad)"
+      InvariantGlobalization="$(InvariantGlobalization)"
+      LoadAllICUData="$(_BlazorWebAssemblyLoadAllGlobalizationData)" />
+
+    <ItemGroup>
+      <FileWrites Include="$(IntermediateOutputPath)blazor.publish.boot.json" />
+    </ItemGroup>
+
+  </Target>
+
+  <Target Name="_BlazorWasmNative"
+      DependsOnTargets="_EnsureWasmRuntimeWorkload;WasmTriggerPublishApp;_ScrambleDotnetJsFileNameForPublish"
+      BeforeTargets="ProcessPublishFilesForBlazor"
+      Condition="'$(UsingBrowserRuntimeWorkload)' == 'true'" />
+
+  <Target Name="_EnsureWasmRuntimeWorkload" Condition="'$(UsingBlazorAOTWorkloadManifest)' != 'true'">
+    <Error
+      Text="Publishing with AOT enabled requires the .NET WebAssembly AOT workload to be installed. To learn more, visit https://aka.ms/AAb4uzl."
+      Code="BLAZORSDK1002" />
+  </Target>
+
+  <Target Name="_GatherWasmFilesToPublish">
+    <ItemGroup>
+      <WasmAssembliesToBundle Remove="@(WasmAssembliesToBundle)" />
+      <WasmAssembliesToBundle Include="%(ResolvedFileToPublish.FullPath)" Exclude="@(_Exclude)" Condition="%(Extension) == '.dll'" />
+      <WasmAssembliesToBundle Condition="'%(WasmAssembliesToBundle.FileName)' == 'Microsoft.JSInterop.WebAssembly'" AOT_InternalForceToInterpret="true" />
+    </ItemGroup>
+  </Target>
+
+  <Target Name="_BlazorCompressPublishFiles" AfterTargets="GeneratePublishBlazorBootJson" Condition="'$(BlazorEnableCompression)' != 'false'">
+    <PropertyGroup>
+      <_CompressedFileOutputPath>$(IntermediateOutputPath)compress\</_CompressedFileOutputPath>
+      <_BlazorWebAssemblyBrotliIncremental>true</_BlazorWebAssemblyBrotliIncremental>
+    </PropertyGroup>
+
+    <PropertyGroup Condition="'$(DOTNET_HOST_PATH)' == ''">
+      <_DotNetHostDirectory>$(NetCoreRoot)</_DotNetHostDirectory>
+      <_DotNetHostFileName>dotnet</_DotNetHostFileName>
+      <_DotNetHostFileName Condition="'$(OS)' == 'Windows_NT'">dotnet.exe</_DotNetHostFileName>
+    </PropertyGroup>
+
+    <Message Text="Compressing Blazor WebAssembly publish artifacts. This may take a while..." Importance="High" />
+
+    <MakeDir Directories="$(_CompressedFileOutputPath)" Condition="!Exists('$(_CompressedFileOutputPath)')" />
+
+    <ItemGroup>
+      <_GzipFileToCompressForPublish Include="@(StaticWebAsset)"
+        Condition="'%(AssetKind)' != 'Build' and ('%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' or '%(StaticWebAsset.AssetTraitName)' == 'Culture')" >
+        <RelatedAsset>%(Identity)</RelatedAsset>
+        <AssetRole>Alternative</AssetRole>
+        <AssetTraitName>Content-Encoding</AssetTraitName>
+        <AssetTraitValue>gzip</AssetTraitValue>
+      </_GzipFileToCompressForPublish>
+
+      <_BrotliFileToCompressForPublish Include="@(_GzipFileToCompressForPublish)" Condition="'%(AssetKind)' != 'Build'">
+        <AssetTraitValue>br</AssetTraitValue>
+      </_BrotliFileToCompressForPublish>
+
+      <!-- We compressed a bunch of assets that were not modified since the build process. We can reuse those and avoid the extra compression we just
+           need to check that they are still relevant (we have updated existing assets to account for linking) -->
+      <_AlreadyGzipCompressedAssets
+        Include="@(StaticWebAsset)"
+        Condition="'%(AssetKind)' != 'Build' and ('%(StaticWebAsset.AssetTraitName)' == 'Content-Encoding' and '%(StaticWebAsset.AssetTraitValue)' == 'gzip')" />
+      <_GzipFileToCompressForPublish Remove="@(_AlreadyGzipCompressedAssets->'%(RelatedAsset)')" />
+    </ItemGroup>
+
+    <GZipCompress
+      FilesToCompress="@(_GzipFileToCompressForPublish)"
+      OutputDirectory="$(_CompressedFileOutputPath)">
+
+      <Output TaskParameter="CompressedFiles" ItemName="_BlazorPublishGZipCompressedFile" />
+      <Output TaskParameter="CompressedFiles" ItemName="FileWrites" />
+    </GZipCompress>
+
+    <BrotliCompress
+      OutputDirectory="$(_CompressedFileOutputPath)"
+      FilesToCompress="@(_BrotliFileToCompressForPublish)"
+      CompressionLevel="$(_BlazorBrotliCompressionLevel)"
+      SkipIfOutputIsNewer="$(_BlazorWebAssemblyBrotliIncremental)"
+      ToolAssembly="$(_BlazorWebAssemblySdkToolAssembly)"
+      ToolExe="$(_DotNetHostFileName)"
+      ToolPath="$(_DotNetHostDirectory)">
+
+      <Output TaskParameter="CompressedFiles" ItemName="_BlazorPublishBrotliCompressedFile" />
+      <Output TaskParameter="CompressedFiles" ItemName="FileWrites" />
+    </BrotliCompress>
+
+    <ItemGroup>
+      <_BlazorPublishGZipCompressedFile>
+        <OriginalItemSpec>%(RelatedAsset)</OriginalItemSpec>
+      </_BlazorPublishGZipCompressedFile>
+      <_BlazorPublishBrotliCompressedFile>
+        <OriginalItemSpec>%(RelatedAsset)</OriginalItemSpec>
+      </_BlazorPublishBrotliCompressedFile>
+
+      <StaticWebAsset Include="@(_BlazorPublishGZipCompressedFile->'%(FullPath)')" />
+      <StaticWebAsset Include="@(_BlazorPublishBrotliCompressedFile->'%(FullPath)')" />
+    </ItemGroup>
+  </Target>
+
+</Project>
index 854ea04..312b530 100644 (file)
@@ -1,3 +1,58 @@
+# Wasm app build
+
+This usually consists of taking the built assemblies, and related files, and generating an app bundle.
+
+Wasm app build can run in two scenarios:
+
+1. After build, eg. when running the app in VS, or `dotnet build foo.csproj`
+2. For Publish, eg, when publishing the app to a folder, or Azure
+
+A dotnet wasm app has some native wasm files (`dotnet.wasm`, and `dotnet.js`). How these files are obtained, or generated:
+
+1. Build
+    a. with no native libraries referenced (AOT setting is ignored here)
+        - files from the runtime pack are used as-is
+    b. with native libraries referenced
+        - dotnet.wasm is relinked with the native libraries
+2. Publish
+    - dotnet.wasm is relinked with the native libraries, and updated pinvoke/icalls from the trimmed assemblies
+    - if `RunAOTCompilation=true`, then the relinking includes AOT'ed assemblies
+
+## `Build`
+
+Implementation:
+
+- Target `WasmBuildApp`
+- runs after `Build` by default
+    - which can be disabled by `$(DisableAutoWasmBuildApp)`
+    - or the run-after target can be set via `$(WasmBuildAppAfterThisTarget)`
+
+- To run a custom target
+    - *before* any of the wasm build targets, use `$(WasmBuildAppDependsOn)`, and prepend your target name to that
+    - *after* any of the wasm build targets, use `AfterTargets="WasmBuildApp"` on that target
+- Avoid depending on this target, because it is available only when the workload is installed. Use `$(WasmNativeWorkload)` to check if it is installed.
+
+## `Publish`
+
+Implementation:
+
+- This part runs as a nested build using a `MSBuild` task, which means that the project gets reevaluated. So, if there were any changes made to items/properties in targets before this, then they won't be visible in the nested build.
+- By default `WasmTriggerPublishApp` runs after the `Publish` target, and that triggers the nested build
+    - The nested build runs `WasmNestedPublishApp`, which causes `Build`, and `Publish` targets to be run
+    - Because this causes `Build` to be run again, if you have any targets that get triggered by that, then they will be running twice.
+        - But the original *build* run, and this *publish* run can be differentiated using `$(WasmBuildingForNestedPublish)`
+
+- `WasmTriggerPublishApp` essentially just invokes the nested publish
+    - This runs after `Publish`
+        - which can be disabled by `$(DisableAutoWasmPublishApp)`
+        - or the run-after target can be set via `$(WasmTriggerPublishAppAfterThisTarget)`
+
+    - To influence the wasm build for publish, use `WasmNestedPublishApp`
+        - To run a custom target before it, use `$(WasmNestedPublishAppDependsOn)`
+        - to run a custom target *after* it, use `AfterTargets="WasmNestedPublishApp"`
+
+    - If you want to *dependsOn* on this, then use `DependsOnTargets="WasmTriggerPublishApp"`
+
 # `WasmApp.{props,targets}`, and `WasmApp.InTree.{props,targets}`
 
 - Any project that wants to use this, can import the props+targets, and set up the
@@ -74,4 +129,4 @@ them for the new task assembly.
 3. Make changes similar to the one for existing dependent tasks in
    - `eng/testing/linker/trimmingTests.targets`,
    - `src/tests/Common/wasm-test-runner/WasmTestRunner.proj`
-   - `src/tests/Directory.Build.targets`
\ No newline at end of file
+   - `src/tests/Directory.Build.targets`
index b345da9..f8c2608 100644 (file)
@@ -17,7 +17,7 @@
                            Condition="'$(_LocalMicrosoftNetCoreAppRuntimePackDir)' != '' and
                                       '%(ResolvedRuntimePack.FrameworkName)' == 'Microsoft.NETCore.App'" />
     </ItemGroup>
-    <Message Text="Used runtime pack: %(ResolvedRuntimePack.PackageDirectory)" Importance="high" />
+    <Message Text="Used runtime pack: %(ResolvedRuntimePack.PackageDirectory) for $(MSBuildProjectName)" Importance="normal" />
   </Target>
 
   <Target Name="RebuildWasmAppBuilder">
@@ -35,9 +35,9 @@
   </Target>
 
   <Target Name="CopyAppZipToHelixTestDir"
-          Condition="'$(WasmCopyAppZipToHelixTestDir)' == 'true'"
+          Condition="'$(WasmCopyAppZipToHelixTestDir)' == 'true' and '$(WasmBuildingForNestedPublish)' != 'true'"
           AfterTargets="Build"
-          DependsOnTargets="Publish">
+          DependsOnTargets="WasmTriggerPublishApp">
     <PropertyGroup>
       <WasmHelixTestAppRelativeDir Condition="'$(WasmHelixTestAppRelativeDir)' == ''">$(MSBuildProjectName)</WasmHelixTestAppRelativeDir>
       <!-- Helix properties -->
index 23f5249..47559bc 100644 (file)
@@ -25,7 +25,7 @@
   <UsingTask TaskName="RuntimeConfigParserTask" AssemblyFile="$(RuntimeConfigParserTasksAssemblyPath)" />
 
   <PropertyGroup>
-    <PublishTrimmed>true</PublishTrimmed>
+    <PublishTrimmed Condition="'$(PublishTrimmed)' == ''">true</PublishTrimmed>
     <TrimMode>link</TrimMode>
   </PropertyGroup>
 
@@ -45,7 +45,7 @@
                            Condition="'$(MicrosoftNetCoreAppRuntimePackLocationToUse)' != '' and
                                       '%(ResolvedRuntimePack.FrameworkName)' == 'Microsoft.NETCore.App'" />
     </ItemGroup>
-    <Message Text="Used runtime pack: %(ResolvedRuntimePack.PackageDirectory)" Importance="high" />
+    <Message Text="Used runtime pack: %(ResolvedRuntimePack.PackageDirectory) for $(MSBuildProjectName)" Importance="normal" />
   </Target>
 
   <!-- the actual properties need to get set in the props, so because UsingTasks depend on those. -->
index 5b43374..1c28283 100644 (file)
       _CompleteWasmBuildNative
     </_WasmBuildNativeCoreDependsOn>
 
-    <WasmBuildNativeOnlyDependsOn>
-      _InitializeCommonProperties;
-      _PrepareForWasmBuildNativeOnly;
-      _WasmBuildNativeCore;
-    </WasmBuildNativeOnlyDependsOn>
-
     <_BeforeWasmBuildAppDependsOn>
       $(_BeforeWasmBuildAppDependsOn);
       _SetupEmscripten;
 
   <Import Project="$(MSBuildThisFileDirectory)EmSdkRepo.Defaults.props" Condition="'$(WasmUseEMSDK_PATH)' == 'true'" />
 
-  <!-- "public" target meant for use outside the regular wasm app generation process FIXME: rename please! -->
-  <Target Name="WasmBuildNativeOnly" DependsOnTargets="$(WasmBuildNativeOnlyDependsOn)" Condition="'$(WasmBuildNative)' == 'true'" />
-
-  <Target Name="_PrepareForWasmBuildNativeOnly">
-    <ItemGroup>
-      <_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)" />
-      <_WasmAssembliesInternal Include="@(WasmAssembliesToBundle->Distinct())" />
-    </ItemGroup>
-  </Target>
-
   <Target Name="_SetupEmscripten">
     <PropertyGroup>
       <_EMSDKMissingPaths Condition="'$(_EMSDKMissingPaths)' == '' and ('$(EmscriptenSdkToolsPath)' == '' or !Exists('$(EmscriptenSdkToolsPath)'))">%24(EmscriptenSdkToolsPath)=$(EmscriptenSdkToolsPath) </_EMSDKMissingPaths>
     <Error Condition="'$(RunAOTCompilation)' == 'true' and '$(_IsEMSDKMissing)' == 'true'"
            Text="$(_EMSDKMissingErrorMessage) Emscripten SDK is required for AOT'ing assemblies." />
 
-    <PropertyGroup>
+    <!-- When Building -->
+    <PropertyGroup Condition="'$(WasmBuildingForNestedPublish)' != 'true'">
+      <!-- build AOT, only if explicitly requested -->
+      <WasmBuildNative Condition="'$(RunAOTCompilation)' == 'true' and '$(RunAOTCompilationAfterBuild)' == 'true'">true</WasmBuildNative>
+
+      <WasmBuildNative Condition="'$(WasmBuildNative)' == '' and @(NativeFileReference->Count()) > 0" >true</WasmBuildNative>
+      <WasmBuildNative Condition="'$(WasmBuildNative)' == ''">false</WasmBuildNative>
+    </PropertyGroup>
+
+    <!-- When Publishing -->
+    <PropertyGroup Condition="'$(WasmBuildingForNestedPublish)' == 'true'">
+      <!-- AOT==true overrides WasmBuildNative -->
       <WasmBuildNative Condition="'$(RunAOTCompilation)' == 'true'">true</WasmBuildNative>
       <WasmBuildNative Condition="'$(WasmBuildNative)' == '' and @(NativeFileReference->Count()) > 0" >true</WasmBuildNative>
-      <WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(PublishTrimmed)' != 'true'"   >false</WasmBuildNative>
-      <WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(Configuration)' == 'Release'" >true</WasmBuildNative>
+
+      <!-- not aot, not trimmed app, no reason to relink -->
+      <WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(PublishTrimmed)' != 'true'">false</WasmBuildNative>
+
+      <!-- default to relinking in Release config -->
+      <WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(Configuration)' == 'Release'">true</WasmBuildNative>
+
       <WasmBuildNative Condition="'$(WasmBuildNative)' == ''">false</WasmBuildNative>
     </PropertyGroup>
 
       <_WasmPInvokeHPath>$(_WasmRuntimePackIncludeDir)wasm\pinvoke.h</_WasmPInvokeHPath>
       <_DriverGenCPath>$(_WasmIntermediateOutputPath)driver-gen.c</_DriverGenCPath>
 
-      <_DriverGenCNeeded Condition="'$(_DriverGenCNeeded)' == '' and '$(RunAOTCompilation)' == 'true'">true</_DriverGenCNeeded>
+      <_DriverGenCNeeded Condition="'$(_DriverGenCNeeded)' == '' and '$(_WasmShouldAOT)' == 'true'">true</_DriverGenCNeeded>
 
       <_EmccAssertionLevelDefault>0</_EmccAssertionLevelDefault>
       <_EmccOptimizationFlagDefault Condition="'$(_WasmDevel)' == 'true'">-O0 -s ASSERTIONS=$(_EmccAssertionLevelDefault)</_EmccOptimizationFlagDefault>
       <_EmccCFlags Include="$(EmccCompileOptimizationFlag)" />
       <_EmccCFlags Include="@(_EmccCommonFlags)" />
 
-      <_EmccCFlags Include="-DENABLE_AOT=1"                    Condition="'$(RunAOTCompilation)' == 'true'" />
-      <_EmccCFlags Include="-DDRIVER_GEN=1"                    Condition="'$(RunAOTCompilation)' == 'true'" />
+      <_EmccCFlags Include="-DENABLE_AOT=1"                    Condition="'$(_WasmShouldAOT)' == 'true'" />
+      <_EmccCFlags Include="-DDRIVER_GEN=1"                    Condition="'$(_WasmShouldAOT)' == 'true'" />
       <_EmccCFlags Include="-DINVARIANT_GLOBALIZATION=1"       Condition="'$(InvariantGlobalization)' == 'true'" />
       <_EmccCFlags Include="-DLINK_ICALLS=1"                   Condition="'$(WasmLinkIcalls)' == 'true'" />
       <_EmccCFlags Include="-DCORE_BINDINGS" />
     <PInvokeTableGenerator
       Modules="@(_WasmPInvokeModules)"
       Assemblies="@(_WasmAssembliesInternal)"
-      OutputPath="$(_WasmPInvokeTablePath)" />
+      OutputPath="$(_WasmPInvokeTablePath)">
+      <Output TaskParameter="FileWrites" ItemName="FileWrites" />
+    </PInvokeTableGenerator>
   </Target>
 
   <Target Name="_GenerateICallTable" Condition="'$(WasmLinkIcalls)' == 'true'">
            Text="Could not find AOT cross compiler at %24(_MonoAotCrossCompilerPath)=$(_MonoAotCrossCompilerPath)" />
 
     <Exec Command='"$(_MonoAotCrossCompilerPath)" --print-icall-table > "$(_WasmRuntimeICallTablePath)"' />
+    <ItemGroup>
+      <FileWrites Include="$(_WasmRuntimeICallTablePath)" />
+    </ItemGroup>
+
     <IcallTableGenerator
       RuntimeIcallTableFile="$(_WasmRuntimeICallTablePath)"
       Assemblies="@(_WasmAssembliesInternal)"
-      OutputPath="$(_WasmICallTablePath)" />
+      OutputPath="$(_WasmICallTablePath)">
+      <Output TaskParameter="FileWrites" ItemName="FileWrites" />
+    </IcallTableGenerator>
   </Target>
 
   <Target Name="_WasmSelectRuntimeComponentsForLinking" Condition="'$(WasmNativeWorkload)' == true" DependsOnTargets="_MonoSelectRuntimeComponents" />
     </ItemGroup>
 
     <WriteLinesToFile Lines="@(_EmccCFlags)" File="$(_EmccCompileRsp)" Overwrite="true" WriteOnlyWhenDifferent="true" />
+    <ItemGroup>
+      <FileWrites Include="$(_EmccCompileRsp)" />
+    </ItemGroup>
 
     <!-- warm up the cache -->
     <Exec Command="$(_EmBuilder) build MINIMAL" EnvironmentVariables="@(EmscriptenEnvVars)" StandardOutputImportance="Low" StandardErrorImportance="Low" />
           SourceFiles="@(_WasmSourceFileToCompile)"
           Arguments='"@$(_EmccDefaultFlagsRsp)" "@$(_EmccCompileRsp)"'
           EnvironmentVariables="@(EmscriptenEnvVars)"
-          OutputMessageImportance="$(_EmccCompileOutputMessageImportance)" />
+          OutputMessageImportance="$(_EmccCompileOutputMessageImportance)">
+      <Output TaskParameter="OutputFiles" ItemName="FileWrites" />
+    </EmccCompile>
   </Target>
 
   <Target Name="_WasmCompileAssemblyBitCodeFilesForAOT"
           Inputs="@(_BitcodeFile);$(_EmccDefaultFlagsRsp);$(_EmccCompileBitcodeRsp)"
           Outputs="@(_BitcodeFile->'%(ObjectFile)')"
-          Condition="'$(RunAOTCompilation)' == 'true' and @(_BitcodeFile->Count()) > 0"
+          Condition="'$(_WasmShouldAOT)' == 'true' and @(_BitcodeFile->Count()) > 0"
           DependsOnTargets="_WasmWriteRspForCompilingBitcode">
 
     <ItemGroup>
           SourceFiles="@(_BitCodeFile)"
           Arguments="&quot;@$(_EmccDefaultFlagsRsp)&quot; &quot;@$(_EmccCompileBitcodeRsp)&quot;"
           EnvironmentVariables="@(EmscriptenEnvVars)"
-          OutputMessageImportance="$(_EmccCompileOutputMessageImportance)" />
+          OutputMessageImportance="$(_EmccCompileOutputMessageImportance)">
+      <Output TaskParameter="OutputFiles" ItemName="FileWrites" />
+    </EmccCompile>
   </Target>
 
   <Target Name="_WasmWriteRspForCompilingBitcode">
       <_BitcodeLDFlags Include="$(EmccExtraBitcodeLDFlags)" />
     </ItemGroup>
     <WriteLinesToFile Lines="@(_BitcodeLDFlags)" File="$(_EmccCompileBitcodeRsp)" Overwrite="true" WriteOnlyWhenDifferent="true" />
+    <ItemGroup>
+      <FileWrites Include="$(_EmccCompileBitcodeRsp)" />
+    </ItemGroup>
   </Target>
 
   <Target Name="_WasmWriteRspFilesForLinking">
       <_WasmNativeFileForLinking Include="%(_WasmSourceFileToCompile.ObjectFile)" />
 
       <_WasmNativeFileForLinking
-          Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)\*.a"
-          Exclude="@(_MonoRuntimeComponentDontLink->'$(MicrosoftNetCoreAppRuntimePackRidNativeDir)\%(Identity)')" />
+          Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)*.a"
+          Exclude="@(_MonoRuntimeComponentDontLink->'$(MicrosoftNetCoreAppRuntimePackRidNativeDir)%(Identity)')" />
 
       <_WasmExtraJSFile Include="@(Content)" Condition="'%(Content.Extension)' == '.js'" />
 
     </ItemGroup>
 
     <WriteLinesToFile Lines="@(_EmccLinkStepArgs)" File="$(_EmccLinkRsp)" Overwrite="true" WriteOnlyWhenDifferent="true" />
+    <ItemGroup>
+      <FileWrites Include="$(_EmccLinkRsp)" />
+    </ItemGroup>
   </Target>
 
   <Target Name="_WasmLinkDotNet"
     <Message Text="Linking with emcc. This may take a while ..." Importance="High" />
     <Message Text="Running emcc with @(_EmccLinkStepArgs->'%(Identity)', ' ')" Importance="Low" />
     <Exec Command='emcc "@$(_EmccDefaultFlagsRsp)" "@$(_EmccLinkRsp)"' EnvironmentVariables="@(EmscriptenEnvVars)" />
+    <ItemGroup>
+      <FileWrites Include="$(_WasmIntermediateOutputPath)dotnet.wasm" />
+      <FileWrites Include="$(_WasmIntermediateOutputPath)dotnet.js" />
+    </ItemGroup>
 
     <Message Text="Optimizing dotnet.wasm ..." Importance="High" />
     <Exec Command='wasm-opt$(_ExeExt) --strip-dwarf "$(_WasmIntermediateOutputPath)dotnet.wasm" -o "$(_WasmIntermediateOutputPath)dotnet.wasm"' Condition="'$(WasmNativeStrip)' == 'true'" IgnoreStandardErrorWarningFormat="true" EnvironmentVariables="@(EmscriptenEnvVars)" />
     </ItemGroup>
   </Target>
 
-  <Target Name="_GenerateDriverGenC" Condition="'$(RunAOTCompilation)' != 'true' and '$(WasmProfilers)' != ''">
+  <Target Name="_GenerateDriverGenC" Condition="'$(_WasmShouldAOT)' != 'true' and '$(WasmProfilers)' != ''">
     <PropertyGroup>
       <EmccExtraCFlags>$(EmccExtraCFlags) -DDRIVER_GEN=1</EmccExtraCFlags>
       <_DriverGenCNeeded>true</_DriverGenCNeeded>
@@ -414,7 +439,7 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
       *******************************
   -->
 
-  <Target Name="_WasmAotCompileApp" Condition="'$(RunAOTCompilation)' == 'true'">
+  <Target Name="_WasmAotCompileApp" Condition="'$(_WasmShouldAOT)' == 'true'">
     <PropertyGroup>
       <!-- FIXME: do it once -->
       <_MonoAotCrossCompilerPath>@(MonoAotCrossCompiler->WithMetadataValue('RuntimeIdentifier','browser-wasm'))</_MonoAotCrossCompilerPath>
@@ -433,13 +458,11 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
       <MonoAOTCompilerDefaultAotArguments Include="deterministic" />
     </ItemGroup>
     <ItemGroup>
-      <_AotInputAssemblies Include="@(_WasmAssembliesInternal)" Condition="'%(_WasmAssembliesInternal._InternalForceInterpret)' != 'true'">
+      <_AotInputAssemblies Include="@(_WasmAssembliesInternal)">
         <AotArguments>@(MonoAOTCompilerDefaultAotArguments, ';')</AotArguments>
         <ProcessArguments>@(MonoAOTCompilerDefaultProcessArguments, ';')</ProcessArguments>
       </_AotInputAssemblies>
 
-      <_AOT_InternalForceInterpretAssemblies Include="@(_WasmAssembliesInternal->WithMetadataValue('_InternalForceInterpret', 'true'))" />
-
       <_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)" />
 
       <_WasmAOTSearchPaths Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)" />
@@ -452,16 +475,13 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
       <_AOTCompilerCacheFile>$(_WasmIntermediateOutputPath)aot_compiler_cache.json</_AOTCompilerCacheFile>
     </PropertyGroup>
 
-    <Error Condition="'$(AOTMode)' == 'llvmonly' and @(_AOT_InternalForceInterpretAssemblies->Count()) > 0"
-           Text="Builing in AOTMode=LLVMonly, but found some assemblies marked as _InternalForceInterpret: @(_AOT_InternalForceInterpretAssemblies)" />
-
     <Message Text="AOT'ing @(_AotInputAssemblies->Count()) assemblies" Importance="High" />
 
     <!-- Dedup -->
     <PropertyGroup Condition="'$(WasmDedup)' == 'true'">
       <_WasmDedupAssembly>$(_WasmIntermediateOutputPath)\aot-instances.dll</_WasmDedupAssembly>
     </PropertyGroup>
-    <WriteLinesToFile Condition="'$(WasmDedup)' == 'true'" File="$(_WasmIntermediateOutputPath)/aot-instances.cs" Overwrite="true" Lines="" />
+    <WriteLinesToFile Condition="'$(WasmDedup)' == 'true'" File="$(_WasmIntermediateOutputPath)/aot-instances.cs" Overwrite="true" Lines="" WriteOnlyWhenDifferent="true" />
     <Csc
       Condition="'$(WasmDedup)' == 'true'"
       Sources="$(_WasmIntermediateOutputPath)\aot-instances.cs"
@@ -494,34 +514,30 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
       DedupAssembly="$(_WasmDedupAssembly)"
       CacheFilePath="$(_AOTCompilerCacheFile)"
       LLVMDebug="dwarfdebug"
-      LLVMPath="$(EmscriptenUpstreamBinPath)" >
+      LLVMPath="$(EmscriptenUpstreamBinPath)"
+      IntermediateOutputPath="$(_WasmIntermediateOutputPath)">
 
       <Output TaskParameter="CompiledAssemblies" ItemName="_WasmAssembliesInternal" />
       <Output TaskParameter="FileWrites" ItemName="FileWrites" />
     </MonoAOTCompiler>
 
     <ItemGroup>
-      <!-- Add back the interpreter-only assemblies -->
-      <_WasmAssembliesInternal Include="@(_AOT_InternalForceInterpretAssemblies)" />
-
-      <_AOTAssemblies Include="@(_WasmAssembliesInternal)" Condition="'%(_WasmAssembliesInternal._InternalForceInterpret)' != 'true'" />
       <_BitcodeFile Include="%(_WasmAssembliesInternal.LlvmBitcodeFile)" />
       <_BitcodeFile ObjectFile="$(_WasmIntermediateOutputPath)%(FileName).o" />
     </ItemGroup>
   </Target>
 
   <!-- '$(ArchiveTests)' != 'true' is to skip on CI for now -->
-  <Target Name="_WasmStripAOTAssemblies" Condition="'$(RunAOTCompilation)' == 'true' and '$(WasmStripAOTAssemblies)' == 'true' and '$(AOTMode)' != 'LLVMOnlyInterp' and '$(ArchiveTests)' != 'true'">
+  <Target Name="_WasmStripAOTAssemblies" Condition="'$(_WasmShouldAOT)' == 'true' and '$(WasmStripAOTAssemblies)' == 'true' and '$(AOTMode)' != 'LLVMOnlyInterp' and '$(ArchiveTests)' != 'true'">
     <PropertyGroup>
       <_WasmStrippedAssembliesPath>$([MSBuild]::NormalizeDirectory($(_WasmIntermediateOutputPath), 'stripped-assemblies'))</_WasmStrippedAssembliesPath>
     </PropertyGroup>
 
     <ItemGroup>
+      <_AOTedAssemblies Include="@(_WasmAssembliesInternal)" />
       <_WasmStrippedAssemblies
-              Condition="'%(_WasmAssembliesInternal._InternalForceInterpret)' != 'true'"
-              Include="@(_WasmAssembliesInternal->'$(_WasmStrippedAssembliesPath)%(FileName)%(Extension)')"
+              Include="@(_AOTedAssemblies)"
               OriginalPath="%(_WasmAssembliesInternal.Identity)" />
-      <_WasmInterpOnlyAssembly Include="@(_WasmAssembliesInternal->WithMetadataValue('_InternalForceInterpret', 'true'))" />
     </ItemGroup>
 
     <!-- Run mono-cil-strip on the assemblies -->
@@ -531,7 +547,7 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
 
     <ItemGroup>
       <_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)" />
-      <_WasmAssembliesInternal Include="@(_WasmStrippedAssemblies);@(_WasmInterpOnlyAssembly)" />
+      <_WasmAssembliesInternal Include="@(_WasmStrippedAssemblies)" />
     </ItemGroup>
   </Target>
 
index bc45a6d..7fdde27 100644 (file)
@@ -5,8 +5,7 @@
     <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
     <UseMonoRuntime>true</UseMonoRuntime>
 
-    <WasmBuildAppAfterThisTarget Condition="'$(WasmBuildAppAfterThisTarget)' == ''">Publish</WasmBuildAppAfterThisTarget>
-    <WasmBuildAppDependsOn>
+    <_WasmBuildCoreDependsOn>
         _InitializeCommonProperties;
         _BeforeWasmBuildApp;
         _WasmResolveReferences;
         _WasmBuildNativeCore;
         _WasmGenerateAppBundle;
         _AfterWasmBuildApp
+    </_WasmBuildCoreDependsOn>
+
+    <WasmBuildAppDependsOn>
+      _PrepareForAfterBuild;
+      $(_WasmBuildCoreDependsOn)
     </WasmBuildAppDependsOn>
+
+    <WasmNestedPublishAppDependsOn>
+      _PrepareForNestedPublish;
+      $(_WasmBuildCoreDependsOn)
+    </WasmNestedPublishAppDependsOn>
   </PropertyGroup>
 </Project>
index e5ceb13..9c96d75 100644 (file)
@@ -5,9 +5,6 @@
   <!--
       Required public items/properties:
       - $(WasmMainJSPath)
-      - @(WasmAssembliesToBundle)    - list of assemblies to package as the wasm app
-          - %(_InternalForceInterpret) metadata   - if true, then skips this assembly from the AOT step.
-                                                    Error for this to be set with AOTMode=LLVMOnly
 
       - $(EMSDK_PATH)      - points to the emscripten sdk location.
 
@@ -45,9 +42,6 @@
       - $(WasmStripAOTAssemblies)           - Whether to run `mono-cil-strip` on the assemblies.
                                               Always set to false!
 
-      - $(WasmBuildAppAfterThisTarget)      - This target is used as `AfterTargets` for `WasmBuildApp. this
-                                              is what triggers the wasm app building. Defaults to `Publish`.
-
       - $(EmccVerbose)                      - Set to false to disable verbose emcc output.
 
       - $(EmccLinkOptimizationFlag)         - Optimization flag to use for the link step
       - $(EmccExtraCFlags)                  - Extra emcc flags for compiling native files
       - $(EmccTotalMemory)                  - Total memory specified with `emcc`. Default value: 536870912
 
+      - $(WasmBuildAppAfterThisTarget)      - This target is used as `AfterTargets` for `WasmBuildApp. this
+                                              is what triggers the wasm app building. Defaults to `Build`.
+      - $(WasmTriggerPublishAppAfterThisTarget) - This target is used as `AfterTargets` for `WasmTriggerPublishApp.
+                                                  Defaults to `Publish`.
+
+      - $(EnableDefaultWasmAssembliesToBundle) - Get list of assemblies to bundle automatically. Defaults to true.
+      - $(WasmBuildOnlyAfterPublish)        - Causes relinking to be done only for Publish. Defaults to false.
+      - $(RunAOTCompilationAfterBuild)      - Run AOT compilation even after Build. By default, it is run only for publish.
+                                              Defaults to false.
 
       Public items:
       - @(WasmExtraFilesToDeploy) - Files to copy to $(WasmAppDir).
     <WasmStripAOTAssemblies>false</WasmStripAOTAssemblies>
 
     <_BeforeWasmBuildAppDependsOn />
+
+    <IsWasmProject Condition="'$(IsWasmProject)' == '' and '$(OutputType)' != 'Library'">true</IsWasmProject>
+    <WasmBuildAppAfterThisTarget Condition="'$(WasmBuildAppAfterThisTarget)' == '' and '$(DisableAutoWasmBuildApp)' != 'true'">Build</WasmBuildAppAfterThisTarget>
+
+    <WasmTriggerPublishAppAfterThisTarget Condition="'$(DisableAutoWasmPublishApp)' != 'true' and '$(WasmBuildingForNestedPublish)' != 'true'">Publish</WasmTriggerPublishAppAfterThisTarget>
+    <_WasmNestedPublishAppPreTarget Condition="'$(DisableAutoWasmPublishApp)' != 'true'">Publish</_WasmNestedPublishAppPreTarget>
+
+    <EnableDefaultWasmAssembliesToBundle Condition="'$(EnableDefaultWasmAssembliesToBundle)' == ''">true</EnableDefaultWasmAssembliesToBundle>
+    <WasmBuildOnlyAfterPublish Condition="'$(WasmBuildOnlyAfterPublish)' == '' and '$(DeployOnBuild)' == 'true'">true</WasmBuildOnlyAfterPublish>
   </PropertyGroup>
 
-  <Import Project="$(MSBuildThisFileDirectory)WasmApp.Native.targets" />
+  <!-- PUBLISH -->
+
+  <Target Name="WasmTriggerPublishApp"
+          AfterTargets="$(WasmTriggerPublishAppAfterThisTarget)"
+          Condition="'$(IsWasmProject)' == 'true' and '$(WasmBuildingForNestedPublish)' != 'true' and '$(IsCrossTargetingBuild)' != 'true'">
+
+    <!-- Use a unique property, so the already run wasm targets can also run -->
+    <MSBuild Projects="$(MSBuildProjectFile)"
+             Targets="WasmNestedPublishApp"
+             Properties="_WasmInNestedPublish_UniqueProperty_XYZ=true;;WasmBuildingForNestedPublish=true;DeployOnBuild=">
+      <Output TaskParameter="TargetOutputs" ItemName="WasmNestedPublishAppResultItems" />
+    </MSBuild>
+
+    <ItemGroup>
+      <WasmAssembliesFinal Remove="@(WasmAssembliesFinal)" />
+      <WasmAssembliesFinal Include="@(WasmNestedPublishAppResultItems)" Condition="'%(WasmNestedPublishAppResultItems.OriginalItemName)' == 'WasmAssembliesFinal'" />
+
+      <WasmNativeAsset Remove="@(WasmNativeAsset)" />
+      <WasmNativeAsset Include="@(WasmNestedPublishAppResultItems)" Condition="'%(WasmNestedPublishAppResultItems.OriginalItemName)' == 'WasmNativeAsset'" />
+
+      <FileWrites Include="@(WasmNestedPublishAppResultItems)" Condition="'%(WasmNestedPublishAppResultItems.OriginalItemName)' == 'FileWrites'" />
+    </ItemGroup>
+  </Target>
+
+  <!-- Public target. Do not depend on this target, as it is meant to be run by a msbuild task -->
+  <Target Name="WasmNestedPublishApp"
+          DependsOnTargets="ResolveRuntimePackAssets;$(_WasmNestedPublishAppPreTarget);$(WasmNestedPublishAppDependsOn)"
+          Condition="'$(WasmBuildingForNestedPublish)' == 'true'"
+          Returns="@(WasmNativeAsset);@(WasmAssembliesFinal);@(FileWrites)">
+
+    <ItemGroup>
+      <WasmNativeAsset OriginalItemName="WasmNativeAsset" />
+      <WasmAssembliesFinal OriginalItemName="WasmAssembliesFinal" />
+      <FileWrites OriginalItemName="FileWrites" />
+    </ItemGroup>
+  </Target>
+
+  <Target Name="_PrepareForNestedPublish" Condition="'$(WasmBuildingForNestedPublish)' == 'true'">
+    <PropertyGroup>
+      <_WasmRuntimeConfigFilePath Condition="$([System.String]::new(%(PublishItemsOutputGroupOutputs.Identity)).EndsWith('$(AssemblyName).runtimeconfig.json'))">@(PublishItemsOutputGroupOutputs)</_WasmRuntimeConfigFilePath>
+    </PropertyGroup>
+
+    <ItemGroup Condition="'$(EnableDefaultWasmAssembliesToBundle)' == 'true' and '$(DisableAutoWasmPublishApp)' != 'true'">
+      <WasmAssembliesToBundle Remove="@(WasmAssembliesToBundle)" />
+      <WasmAssembliesToBundle Include="$(PublishDir)\**\*.dll" />
+    </ItemGroup>
 
-  <!-- Having this separate target allows users to cleanly add After/BeforeTargets for this -->
-  <Target Name="WasmBuildApp" AfterTargets="$(WasmBuildAppAfterThisTarget)" />
+    <PropertyGroup Condition="'$(_WasmRuntimeConfigFilePath)' == ''">
+      <_WasmRuntimeConfigFilePath Condition="$([System.String]::new(%(PublishItemsOutputGroupOutputs.Identity)).EndsWith('$(AssemblyName).runtimeconfig.json'))">@(PublishItemsOutputGroupOutputs)</_WasmRuntimeConfigFilePath>
+    </PropertyGroup>
+  </Target>
+
+  <Import Project="$(MSBuildThisFileDirectory)WasmApp.Native.targets" />
 
-  <Target Name="_WasmCoreBuild" BeforeTargets="WasmBuildApp" DependsOnTargets="$(WasmBuildAppDependsOn)" />
+  <!-- public target for Build -->
+  <Target Name="WasmBuildApp"
+          AfterTargets="$(WasmBuildAppAfterThisTarget)"
+          DependsOnTargets="$(WasmBuildAppDependsOn)"
+          Condition="'$(IsWasmProject)' == 'true' and '$(WasmBuildingForNestedPublish)' == '' and '$(WasmBuildOnlyAfterPublish)' != 'true' and '$(IsCrossTargetingBuild)' != 'true'" />
 
   <Target Name="_InitializeCommonProperties">
     <Error Condition="'$(MicrosoftNetCoreAppRuntimePackDir)' == '' and ('%(ResolvedRuntimePack.PackageDirectory)' == '' or !Exists(%(ResolvedRuntimePack.PackageDirectory)))"
       <_WasmRuntimePackIncludeDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'include'))</_WasmRuntimePackIncludeDir>
       <_WasmRuntimePackSrcDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'src'))</_WasmRuntimePackSrcDir>
 
-      <_WasmIntermediateOutputPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm'))</_WasmIntermediateOutputPath>
+      <_WasmIntermediateOutputPath Condition="'$(WasmBuildingForNestedPublish)' == ''">$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm', 'for-build'))</_WasmIntermediateOutputPath>
+      <_WasmIntermediateOutputPath Condition="'$(WasmBuildingForNestedPublish)' != ''">$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm', 'for-publish'))</_WasmIntermediateOutputPath>
 
       <_DriverGenCPath>$(_WasmIntermediateOutputPath)driver-gen.c</_DriverGenCPath>
+      <_WasmShouldAOT Condition="'$(WasmBuildingForNestedPublish)' == 'true' and '$(RunAOTCompilation)' == 'true'">true</_WasmShouldAOT>
+      <_WasmShouldAOT Condition="'$(RunAOTCompilationAfterBuild)' == 'true' and '$(RunAOTCompilation)' == 'true'">true</_WasmShouldAOT>
+      <_WasmShouldAOT Condition="'$(_WasmShouldAOT)' == ''">false</_WasmShouldAOT>
     </PropertyGroup>
 
     <MakeDir Directories="$(_WasmIntermediateOutputPath)" />
   </Target>
 
+  <Target Name="_PrepareForAfterBuild" Condition="'$(WasmBuildingForNestedPublish)' != 'true'">
+    <ItemGroup Condition="'$(EnableDefaultWasmAssembliesToBundle)' == 'true'">
+      <WasmAssembliesToBundle Include="@(ReferenceCopyLocalPaths);@(MainAssembly)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'" />
+    </ItemGroup>
+  </Target>
+
   <Target Name="_BeforeWasmBuildApp" DependsOnTargets="$(_BeforeWasmBuildAppDependsOn)">
     <Error Condition="!Exists('$(MicrosoftNetCoreAppRuntimePackRidDir)')" Text="MicrosoftNetCoreAppRuntimePackRidDir=$(MicrosoftNetCoreAppRuntimePackRidDir) doesn't exist" />
     <Error Condition="@(WasmAssembliesToBundle->Count()) == 0" Text="WasmAssembliesToBundle item is empty. No assemblies to process" />
 
     <PropertyGroup>
-      <WasmGenerateAppBundle Condition="'$(WasmGenerateAppBundle)' == ''">true</WasmGenerateAppBundle>
+      <WasmGenerateAppBundle Condition="'$(WasmGenerateAppBundle)' == '' and '$(OutputType)' != 'Library'">true</WasmGenerateAppBundle>
+      <WasmGenerateAppBundle Condition="'$(WasmGenerateAppBundle)' == ''">false</WasmGenerateAppBundle>
       <WasmAppDir Condition="'$(WasmAppDir)' == ''">$([MSBuild]::NormalizeDirectory($(OutputPath), 'AppBundle'))</WasmAppDir>
       <WasmMainAssemblyFileName Condition="'$(WasmMainAssemblyFileName)' == ''">$(TargetFileName)</WasmMainAssemblyFileName>
 
       <WasmAppDir>$([MSBuild]::NormalizeDirectory($(WasmAppDir)))</WasmAppDir>
 
       <_MainAssemblyPath Condition="'%(WasmAssembliesToBundle.FileName)' == $(AssemblyName) and '%(WasmAssembliesToBundle.Extension)' == '.dll' and $(WasmGenerateAppBundle) == 'true'">%(WasmAssembliesToBundle.Identity)</_MainAssemblyPath>
-      <_WasmRuntimeConfigFilePath Condition="$(_MainAssemblyPath) != ''">$([System.IO.Path]::ChangeExtension($(_MainAssemblyPath), '.runtimeconfig.json'))</_WasmRuntimeConfigFilePath>
-      <_ParsedRuntimeConfigFilePath Condition="'$(_MainAssemblyPath)' != ''">$([System.IO.Path]::GetDirectoryName($(_MainAssemblyPath)))\runtimeconfig.bin</_ParsedRuntimeConfigFilePath>
+      <_WasmRuntimeConfigFilePath Condition="'$(_WasmRuntimeConfigFilePath)' == '' and $(_MainAssemblyPath) != ''">$([System.IO.Path]::ChangeExtension($(_MainAssemblyPath), '.runtimeconfig.json'))</_WasmRuntimeConfigFilePath>
+      <_ParsedRuntimeConfigFilePath Condition="'$(_WasmRuntimeConfigFilePath)' != ''">$([System.IO.Path]::GetDirectoryName($(_WasmRuntimeConfigFilePath)))\runtimeconfig.bin</_ParsedRuntimeConfigFilePath>
     </PropertyGroup>
 
-    <Warning Condition="'$(WasmGenerateAppBundle)' == 'true' and $(_MainAssemblyPath) == ''" Text="Could not find %24(AssemblyName)=$(AssemblyName) in the assemblies to be bundled.." />
+    <Warning Condition="'$(WasmGenerateAppBundle)' == 'true' and $(_MainAssemblyPath) == ''" Text="Could not find %24(AssemblyName)=$(AssemblyName).dll in the assemblies to be bundled." />
     <Warning Condition="'$(WasmGenerateAppBundle)' == 'true' and $(_WasmRuntimeConfigFilePath) != '' and !Exists($(_WasmRuntimeConfigFilePath))"
              Text="Could not find $(_WasmRuntimeConfigFilePath) for $(_MainAssemblyPath)." />
 
     <ItemGroup>
+      <_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)" />
       <_WasmAssembliesInternal Include="@(WasmAssembliesToBundle->Distinct())" />
 
+      <_WasmSatelliteAssemblies Remove="@(_WasmSatelliteAssemblies)" />
       <_WasmSatelliteAssemblies Include="@(_WasmAssembliesInternal)" />
       <_WasmSatelliteAssemblies Remove="@(_WasmSatelliteAssemblies)" Condition="!$([System.String]::Copy('%(Identity)').EndsWith('.resources.dll'))" />
       <!-- FIXME: Only include the ones with valid culture name -->
     </ItemGroup>
   </Target>
 
-  <Target Name="_WasmGenerateAppBundle" Condition="'$(WasmGenerateAppBundle)' == 'true'" DependsOnTargets="_WasmGenerateRuntimeConfig">
-    <Error Condition="'$(WasmMainJSPath)' == ''" Text="%24(WasmMainJSPath) property needs to be set" />
-
+  <Target Name="_GetWasmGenerateAppBundleDependencies">
     <PropertyGroup>
       <WasmIcuDataFileName Condition="'$(InvariantGlobalization)' != 'true'">icudt.dat</WasmIcuDataFileName>
 
       <WasmNativeAsset Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(WasmIcuDataFileName)" Condition="'$(InvariantGlobalization)' != 'true'" />
       <WasmNativeAsset Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)dotnet.timezones.blat" />
     </ItemGroup>
+  </Target>
 
+  <Target Name="_WasmGenerateAppBundle"
+          Inputs="@(_WasmAssembliesInternal);$(WasmMainJSPath);$(WasmIcuDataFileName);@(WasmNativeAsset)"
+          Outputs="$(WasmAppDir)\.stamp"
+          Condition="'$(WasmGenerateAppBundle)' == 'true'"
+          DependsOnTargets="_WasmGenerateRuntimeConfig;_GetWasmGenerateAppBundleDependencies">
+    <Error Condition="'$(WasmMainJSPath)' == ''" Text="%24(WasmMainJSPath) property needs to be set" />
+
+    <RemoveDir Directories="$(WasmAppDir)" />
     <WasmAppBuilder
       AppDir="$(WasmAppDir)"
       MainJS="$(WasmMainJSPath)"
     </WasmAppBuilder>
 
     <CallTarget Targets="_GenerateRunV8Script" Condition="'$(WasmGenerateRunV8Script)' == 'true'" />
+
+    <WriteLinesToFile File="$(WasmAppDir)\.stamp" Lines="" Overwrite="true" />
   </Target>
 
   <Target Name="_GenerateRunV8Script">
index 3a7b66f..65a0df1 100644 (file)
@@ -5,6 +5,7 @@
     <TestRootDir Condition="'$(HELIX_WORKITEM_ROOT)' == ''">$(MSBuildThisFileDirectory)..\wasm_build\</TestRootDir>
 
     <RunAOTCompilation Condition="'$(RunAOTCompilation)' == ''">true</RunAOTCompilation>
+    <RunAOTCompilationAfterBuild>true</RunAOTCompilationAfterBuild>
     <OriginalPublishDir>$(TestRootDir)..\publish\</OriginalPublishDir>
     <ExtraFilesPath>$(OriginalPublishDir)..\extraFiles\</ExtraFilesPath>
     <IntermediateOutputPath>$(TestRootDir)\obj\</IntermediateOutputPath>
index 0cff1f1..ff88648 100644 (file)
@@ -4,6 +4,7 @@
   <PropertyGroup>
     <TargetFramework>$(AspNetCoreAppCurrent)</TargetFramework>
     <OutputType>Library</OutputType>
+    <IsWasmProject>true</IsWasmProject>
     <Configuration>Debug</Configuration>
     <RuntimeConfiguration Condition="'$(RuntimeConfiguration)'==''">Release</RuntimeConfiguration>
 
index 0503fea..bf098dd 100644 (file)
@@ -5,6 +5,7 @@
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <RunAnalyzers>false</RunAnalyzers>
     <WasmBuildAppDependsOn>PrepareForWasmBuildApp;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
+    <WasmGenerateAppBundle>true</WasmGenerateAppBundle>
   </PropertyGroup>
 
   <ItemGroup>
index 71ae231..4ee7389 100644 (file)
@@ -191,11 +191,15 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
     /// </summary>
     public string? CacheFilePath { get; set; }
 
+    [Required]
+    public string IntermediateOutputPath { get; set; } = string.Empty;
+
     [Output]
     public string[]? FileWrites { get; private set; }
 
     private List<string> _fileWrites = new();
 
+    private IList<ITaskItem>? _assembliesToCompile;
     private ConcurrentDictionary<string, ITaskItem> compiledAssemblies = new();
 
     private MonoAotMode parsedAotMode;
@@ -207,7 +211,7 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
     private int _numCompiled;
     private int _totalNumAssemblies;
 
-    public override bool Execute()
+    private bool ProcessAndValidateArguments()
     {
         if (!File.Exists(CompilerBinaryPath))
         {
@@ -230,6 +234,9 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
             return false;
         }
 
+        if (!Directory.Exists(IntermediateOutputPath))
+            Directory.CreateDirectory(IntermediateOutputPath);
+
         if (AotProfilePath != null)
         {
             foreach (var path in AotProfilePath)
@@ -246,7 +253,7 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
         {
             if (string.IsNullOrEmpty(LLVMPath))
                 // prevent using some random llc/opt from PATH (installed with clang)
-                throw new ArgumentException($"'{nameof(LLVMPath)}' is required when '{nameof(UseLLVM)}' is true.", nameof(LLVMPath));
+                throw new LogAsErrorException($"'{nameof(LLVMPath)}' is required when '{nameof(UseLLVM)}' is true.");
 
             if (!Directory.Exists(LLVMPath))
             {
@@ -270,7 +277,7 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
                 Log.LogWarning($"'{nameof(OutputType)}=Normal' is deprecated, use 'ObjectFile' instead.");
                 parsedOutputType = MonoAotOutputType.ObjectFile; break;
             default:
-                throw new ArgumentException($"'{nameof(OutputType)}' must be one of: '{nameof(MonoAotOutputType.ObjectFile)}', '{nameof(MonoAotOutputType.AsmOnly)}', '{nameof(MonoAotOutputType.Library)}'. Received: '{OutputType}'.", nameof(OutputType));
+                throw new LogAsErrorException($"'{nameof(OutputType)}' must be one of: '{nameof(MonoAotOutputType.ObjectFile)}', '{nameof(MonoAotOutputType.AsmOnly)}', '{nameof(MonoAotOutputType.Library)}'. Received: '{OutputType}'.");
         }
 
         switch (LibraryFormat)
@@ -280,13 +287,13 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
             case "So": parsedLibraryFormat = MonoAotLibraryFormat.So; break;
             default:
                 if (parsedOutputType == MonoAotOutputType.Library)
-                    throw new ArgumentException($"'{nameof(LibraryFormat)}' must be one of: '{nameof(MonoAotLibraryFormat.Dll)}', '{nameof(MonoAotLibraryFormat.Dylib)}', '{nameof(MonoAotLibraryFormat.So)}'. Received: '{LibraryFormat}'.", nameof(LibraryFormat));
+                    throw new LogAsErrorException($"'{nameof(LibraryFormat)}' must be one of: '{nameof(MonoAotLibraryFormat.Dll)}', '{nameof(MonoAotLibraryFormat.Dylib)}', '{nameof(MonoAotLibraryFormat.So)}'. Received: '{LibraryFormat}'.");
                 break;
         }
 
         if (parsedAotMode == MonoAotMode.LLVMOnly && !UseLLVM)
         {
-            throw new ArgumentException($"'{nameof(UseLLVM)}' must be true when '{nameof(Mode)}' is {nameof(MonoAotMode.LLVMOnly)}.", nameof(UseLLVM));
+            throw new LogAsErrorException($"'{nameof(UseLLVM)}' must be true when '{nameof(Mode)}' is {nameof(MonoAotMode.LLVMOnly)}.");
         }
 
         switch (AotModulesTableLanguage)
@@ -294,32 +301,55 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
             case "C": parsedAotModulesTableLanguage = MonoAotModulesTableLanguage.C; break;
             case "ObjC": parsedAotModulesTableLanguage = MonoAotModulesTableLanguage.ObjC; break;
             default:
-                throw new ArgumentException($"'{nameof(AotModulesTableLanguage)}' must be one of: '{nameof(MonoAotModulesTableLanguage.C)}', '{nameof(MonoAotModulesTableLanguage.ObjC)}'. Received: '{AotModulesTableLanguage}'.", nameof(AotModulesTableLanguage));
+                throw new LogAsErrorException($"'{nameof(AotModulesTableLanguage)}' must be one of: '{nameof(MonoAotModulesTableLanguage.C)}', '{nameof(MonoAotModulesTableLanguage.ObjC)}'. Received: '{AotModulesTableLanguage}'.");
         }
 
         if (!string.IsNullOrEmpty(AotModulesTablePath))
         {
             // AOT modules for static linking, needs the aot modules table
             UseStaticLinking = true;
-
-            if (!GenerateAotModulesTable(Assemblies, Profilers, AotModulesTablePath))
-                return false;
         }
 
         if (UseDirectIcalls && !UseStaticLinking)
         {
-            throw new ArgumentException($"'{nameof(UseDirectIcalls)}' can only be used with '{nameof(UseStaticLinking)}=true'.", nameof(UseDirectIcalls));
+            throw new LogAsErrorException($"'{nameof(UseDirectIcalls)}' can only be used with '{nameof(UseStaticLinking)}=true'.");
         }
 
         if (UseDirectPInvoke && !UseStaticLinking)
         {
-            throw new ArgumentException($"'{nameof(UseDirectPInvoke)}' can only be used with '{nameof(UseStaticLinking)}=true'.", nameof(UseDirectPInvoke));
+            throw new LogAsErrorException($"'{nameof(UseDirectPInvoke)}' can only be used with '{nameof(UseStaticLinking)}=true'.");
         }
 
         if (UseStaticLinking && (parsedOutputType == MonoAotOutputType.Library))
         {
-            throw new ArgumentException($"'{nameof(OutputType)}=Library' can not be used with '{nameof(UseStaticLinking)}=true'.", nameof(OutputType));
+            throw new LogAsErrorException($"'{nameof(OutputType)}=Library' can not be used with '{nameof(UseStaticLinking)}=true'.");
+        }
+
+        return !Log.HasLoggedErrors;
+    }
+
+    public override bool Execute()
+    {
+        try
+        {
+            return ExecuteInternal();
+        }
+        catch (LogAsErrorException laee)
+        {
+            Log.LogError(laee.Message);
+            return false;
         }
+    }
+
+    private bool ExecuteInternal()
+    {
+        if (!ProcessAndValidateArguments())
+            return false;
+
+        _assembliesToCompile = EnsureAndGetAssembliesInTheSameDir(Assemblies);
+
+        if (!string.IsNullOrEmpty(AotModulesTablePath) && !GenerateAotModulesTable(_assembliesToCompile, Profilers, AotModulesTablePath))
+            return false;
 
         string? monoPaths = null;
         if (AdditionalAssemblySearchPaths != null)
@@ -329,14 +359,14 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
 
         //FIXME: check the nothing changed at all case
 
-        _totalNumAssemblies = Assemblies.Length;
-        int allowedParallelism = Math.Min(Assemblies.Length, Environment.ProcessorCount);
+        _totalNumAssemblies = _assembliesToCompile.Count;
+        int allowedParallelism = Math.Min(_assembliesToCompile.Count, Environment.ProcessorCount);
         if (BuildEngine is IBuildEngine9 be9)
             allowedParallelism = be9.RequestCores(allowedParallelism);
 
         if (DisableParallelAot || allowedParallelism == 1)
         {
-            foreach (var assemblyItem in Assemblies)
+            foreach (var assemblyItem in _assembliesToCompile)
             {
                 if (!PrecompileLibrarySerial(assemblyItem, monoPaths))
                     return !Log.HasLoggedErrors;
@@ -345,15 +375,12 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
         else
         {
             ParallelLoopResult result = Parallel.ForEach(
-                                                    Assemblies,
+                                                    _assembliesToCompile,
                                                     new ParallelOptions { MaxDegreeOfParallelism = allowedParallelism },
                                                     (assemblyItem, state) => PrecompileLibraryParallel(assemblyItem, monoPaths, state));
 
             if (!result.IsCompleted)
             {
-                if (!Log.HasLoggedErrors)
-                    Log.LogError("Unknown failure occured while compiling");
-
                 return false;
             }
         }
@@ -365,15 +392,71 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
         if (_cache.Save(CacheFilePath!))
             _fileWrites.Add(CacheFilePath!);
 
-        CompiledAssemblies = ConvertAssembliesDictToOrderedList(compiledAssemblies, Assemblies).ToArray();
+        CompiledAssemblies = ConvertAssembliesDictToOrderedList(compiledAssemblies, _assembliesToCompile).ToArray();
         FileWrites = _fileWrites.ToArray();
 
         return !Log.HasLoggedErrors;
     }
 
+    private IList<ITaskItem> EnsureAndGetAssembliesInTheSameDir(ITaskItem[] originalAssemblies)
+    {
+        List<ITaskItem> filteredAssemblies = new();
+        string firstAsmDir = Path.GetDirectoryName(originalAssemblies[0].GetMetadata("FullPath")) ?? string.Empty;
+        bool allInSameDir = true;
+
+        foreach (var origAsm in originalAssemblies)
+        {
+            if (allInSameDir && Path.GetDirectoryName(origAsm.GetMetadata("FullPath")) != firstAsmDir)
+                allInSameDir = false;
+
+            if (ShouldSkip(origAsm))
+            {
+                if (parsedAotMode == MonoAotMode.LLVMOnly)
+                    throw new LogAsErrorException($"Building in AOTMode=LLVMonly is not compatible with excluding any assemblies for AOT. Excluded assembly: {origAsm.ItemSpec}");
+
+                Log.LogMessage(MessageImportance.Low, $"Skipping {origAsm.ItemSpec} because it has %(AOT_InternalForceToInterpret)=true");
+                continue;
+            }
+
+            filteredAssemblies.Add(origAsm);
+        }
+
+        if (allInSameDir)
+            return filteredAssemblies;
+
+        // Copy to aot-in
+
+        string aotInPath = Path.Combine(IntermediateOutputPath, "aot-in");
+        Directory.CreateDirectory(aotInPath);
+
+        List<ITaskItem> newAssemblies = new();
+        foreach (var origAsm in originalAssemblies)
+        {
+            string asmPath = origAsm.GetMetadata("FullPath");
+            string newPath = Path.Combine(aotInPath, Path.GetFileName(asmPath));
+
+            // FIXME: delete files not in originalAssemblies though
+            // FIXME: or .. just delete the whole dir?
+            if (Utils.CopyIfDifferent(asmPath, newPath, useHash: true))
+                Log.LogMessage(MessageImportance.Low, $"Copying {asmPath} to {newPath}");
+
+            if (!ShouldSkip(origAsm))
+            {
+                ITaskItem newAsm = new TaskItem(newPath);
+                origAsm.CopyMetadataTo(newAsm);
+                newAssemblies.Add(newAsm);
+            }
+        }
+
+        return newAssemblies;
+
+        static bool ShouldSkip(ITaskItem asmItem)
+            => bool.TryParse(asmItem.GetMetadata("AOT_InternalForceToInterpret"), out bool skip) && skip;
+    }
+
     private bool PrecompileLibrary(ITaskItem assemblyItem, string? monoPaths)
     {
-        string assembly = assemblyItem.ItemSpec;
+        string assembly = assemblyItem.GetMetadata("FullPath");
         string assemblyDir = Path.GetDirectoryName(assembly)!;
         var aotAssembly = new TaskItem(assembly);
         var aotArgs = new List<string>();
@@ -567,38 +650,20 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
         // values, which wont work.
         processArgs.Add($"\"--aot={string.Join(",", aotArgs)}\"");
 
-        string paths = "";
         if (isDedup)
         {
-            StringBuilder sb = new StringBuilder();
-            HashSet<string> allPaths = new HashSet<string>();
-            foreach (var aItem in Assemblies)
-            {
-                string filename = aItem.ItemSpec;
-                processArgs.Add(filename);
-                string dir = Path.GetDirectoryName(filename)!;
-                if (!allPaths.Contains(dir))
-                {
-                    allPaths.Add(dir);
-                    if (sb.Length > 0)
-                        sb.Append(Path.PathSeparator);
-                    sb.Append(dir);
-                }
-            }
-            if (sb.Length > 0)
-                sb.Append(Path.PathSeparator);
-            sb.Append(monoPaths);
-            paths = sb.ToString();
+            foreach (var aItem in _assembliesToCompile!)
+                processArgs.Add(aItem.ItemSpec);
         }
         else
         {
-            paths = $"{assemblyDir}{Path.PathSeparator}{monoPaths}";
             processArgs.Add('"' + assemblyFilename + '"');
         }
 
+        monoPaths = $"{assemblyDir}{Path.PathSeparator}{monoPaths}";
         var envVariables = new Dictionary<string, string>
         {
-            {"MONO_PATH", paths},
+            {"MONO_PATH", monoPaths },
             {"MONO_ENV_OPTIONS", string.Empty} // we do not want options to be provided out of band to the cross compilers
         };
 
@@ -713,7 +778,7 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
         state.Break();
     }
 
-    private bool GenerateAotModulesTable(ITaskItem[] assemblies, string[]? profilers, string outputFile)
+    private bool GenerateAotModulesTable(IEnumerable<ITaskItem> assemblies, string[]? profilers, string outputFile)
     {
         var symbols = new List<string>();
         foreach (var asm in assemblies)
@@ -831,12 +896,12 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
         }
     }
 
-    private IList<ITaskItem> ConvertAssembliesDictToOrderedList(ConcurrentDictionary<string, ITaskItem> dict, ITaskItem[] items)
+    private static IList<ITaskItem> ConvertAssembliesDictToOrderedList(ConcurrentDictionary<string, ITaskItem> dict, IList<ITaskItem> originalAssemblies)
     {
-        List<ITaskItem> outItems = new(items.Length);
-        foreach (ITaskItem item in items)
+        List<ITaskItem> outItems = new(originalAssemblies.Count);
+        foreach (ITaskItem item in originalAssemblies)
         {
-            if (!dict.TryGetValue(item.ItemSpec, out ITaskItem? dictItem))
+            if (!dict.TryGetValue(item.GetMetadata("FullPath"), out ITaskItem? dictItem))
                 throw new LogAsErrorException($"Bug: Could not find item in the dict with key {item.ItemSpec}");
 
             outItems.Add(dictItem);
@@ -931,23 +996,26 @@ internal class ProxyFile
         if (!_cache.Enabled)
             return true;
 
-        if (!File.Exists(TempFile))
-            throw new LogAsErrorException($"Could not find output file {TempFile}");
-
-        if (!_cache.ShouldCopy(this, out string? cause))
+        try
         {
-            _cache.Log.LogMessage(MessageImportance.Low, $"Skipping copying over {TargetFile} as the contents are unchanged");
-            return false;
-        }
+            if (!_cache.ShouldCopy(this, out string? cause))
+            {
+                _cache.Log.LogMessage(MessageImportance.Low, $"Skipping copying over {TargetFile} as the contents are unchanged");
+                return false;
+            }
 
-        if (File.Exists(TargetFile))
-            File.Delete(TargetFile);
+            if (File.Exists(TargetFile))
+                File.Delete(TargetFile);
 
-        File.Copy(TempFile, TargetFile);
-        File.Delete(TempFile);
+            File.Copy(TempFile, TargetFile);
 
-        _cache.Log.LogMessage(MessageImportance.Low, $"Copying {TempFile} to {TargetFile} because {cause}");
-        return true;
+            _cache.Log.LogMessage(MessageImportance.Low, $"Copying {TempFile} to {TargetFile} because {cause}");
+            return true;
+        }
+        finally
+        {
+            File.Delete(TempFile);
+        }
     }
 }
 
index 2c1a8b4..2c62421 100644 (file)
@@ -212,7 +212,7 @@ internal static class Utils
             throw new ArgumentException($"Cannot find {src} file to copy", nameof(src));
 
         bool areDifferent = !File.Exists(dst) ||
-                                (useHash && Utils.ComputeHash(src) != Utils.ComputeHash(dst)) ||
+                                (useHash && ComputeHash(src) != ComputeHash(dst)) ||
                                 (File.ReadAllText(src) != File.ReadAllText(dst));
 
         if (areDifferent)
index e869e98..a5d6af6 100644 (file)
@@ -45,6 +45,19 @@ namespace Microsoft.WebAssembly.Build.Tasks
 
         public override bool Execute()
         {
+            try
+            {
+                return ExecuteActual();
+            }
+            catch (LogAsErrorException laee)
+            {
+                Log.LogError(laee.Message);
+                return false;
+            }
+        }
+
+        private bool ExecuteActual()
+        {
             if (SourceFiles.Length == 0)
             {
                 Log.LogError($"No SourceFiles to compile");
@@ -133,7 +146,7 @@ namespace Microsoft.WebAssembly.Build.Tasks
                     });
 
                     if (!result.IsCompleted && !Log.HasLoggedErrors)
-                        Log.LogError("Unknown failed occured while compiling");
+                        Log.LogError("Unknown failure occured while compiling. Check logs to get more details.");
                 }
 
                 if (!Log.HasLoggedErrors)
@@ -211,7 +224,7 @@ namespace Microsoft.WebAssembly.Build.Tasks
         private bool ShouldCompile(string srcFile, string objFile, string[] depFiles, out string reason)
         {
             if (!File.Exists(srcFile))
-                throw new ArgumentException($"Could not find source file {srcFile}");
+                throw new LogAsErrorException($"Could not find source file {srcFile}");
 
             if (!File.Exists(objFile))
             {
@@ -228,7 +241,7 @@ namespace Microsoft.WebAssembly.Build.Tasks
                     return true;
             }
 
-            reason = "everything is up-to-date.";
+            reason = "everything is up-to-date";
             return false;
 
             bool IsNewerThanOutput(string inFile, string outFile, out string reason)
index 13c75c3..cc4d8af 100644 (file)
@@ -23,6 +23,9 @@ public class IcallTableGenerator : Task
     [Required, NotNull]
     public string? OutputPath { get; set; }
 
+    [Output]
+    public string? FileWrites { get; private set; } = "";
+
     private List<Icall> _icalls = new List<Icall> ();
     private Dictionary<string, IcallClass> _runtimeIcalls = new Dictionary<string, IcallClass> ();
 
@@ -58,6 +61,7 @@ public class IcallTableGenerator : Task
             Log.LogMessage(MessageImportance.Low, $"Generating icall table to '{OutputPath}'.");
         else
             Log.LogMessage(MessageImportance.Low, $"Icall table in {OutputPath} is unchanged.");
+        FileWrites = OutputPath;
 
         File.Delete(tmpFileName);
     }
index f6b8953..825de67 100644 (file)
@@ -16,20 +16,43 @@ using Microsoft.Build.Utilities;
 
 public class PInvokeTableGenerator : Task
 {
-    [Required]
-    public ITaskItem[]? Modules { get; set; }
-    [Required]
-    public ITaskItem[]? Assemblies { get; set; }
+    [Required, NotNull]
+    public string[]? Modules { get; set; }
+    [Required, NotNull]
+    public string[]? Assemblies { get; set; }
 
     [Required, NotNull]
     public string? OutputPath { get; set; }
 
+    [Output]
+    public string FileWrites { get; private set; } = string.Empty;
+
     private static char[] s_charsToReplace = new[] { '.', '-', };
 
     public override bool Execute()
     {
-        GenPInvokeTable(Modules!.Select(item => item.ItemSpec).ToArray(), Assemblies!.Select(item => item.ItemSpec).ToArray());
-        return true;
+        if (Assemblies.Length == 0)
+        {
+            Log.LogError($"No assemblies given to scan for pinvokes");
+            return false;
+        }
+
+        if (Modules.Length == 0)
+        {
+            Log.LogError($"{nameof(PInvokeTableGenerator)}.{nameof(Modules)} cannot be empty");
+            return false;
+        }
+
+        try
+        {
+            GenPInvokeTable(Modules, Assemblies);
+            return !Log.HasLoggedErrors;
+        }
+        catch (LogAsErrorException laee)
+        {
+            Log.LogError(laee.Message);
+            return false;
+        }
     }
 
     public void GenPInvokeTable(string[] pinvokeModules, string[] assemblies)
@@ -61,6 +84,7 @@ public class PInvokeTableGenerator : Task
             Log.LogMessage(MessageImportance.Low, $"Generating pinvoke table to '{OutputPath}'.");
         else
             Log.LogMessage(MessageImportance.Low, $"PInvoke table in {OutputPath} is unchanged.");
+        FileWrites = OutputPath;
 
         File.Delete(tmpFileName);
     }
@@ -339,11 +363,7 @@ public class PInvokeTableGenerator : Task
             return false;
     }
 
-    private static void Error (string msg)
-    {
-        // FIXME:
-        throw new Exception(msg);
-    }
+    private static void Error (string msg) => throw new LogAsErrorException(msg);
 }
 
 internal class PInvoke
index 57cb7c3..58b4b92 100644 (file)
@@ -118,10 +118,23 @@ public class WasmAppBuilder : Task
 
     public override bool Execute ()
     {
+        try
+        {
+            return ExecuteInternal();
+        }
+        catch (LogAsErrorException laee)
+        {
+            Log.LogError(laee.Message);
+            return false;
+        }
+    }
+
+    private bool ExecuteInternal ()
+    {
         if (!File.Exists(MainJS))
-            throw new ArgumentException($"File MainJS='{MainJS}' doesn't exist.");
+            throw new LogAsErrorException($"File MainJS='{MainJS}' doesn't exist.");
         if (!InvariantGlobalization && string.IsNullOrEmpty(IcuDataFileName))
-            throw new ArgumentException("IcuDataFileName property shouldn't be empty if InvariantGlobalization=false");
+            throw new LogAsErrorException("IcuDataFileName property shouldn't be empty if InvariantGlobalization=false");
 
         if (Assemblies?.Length == 0)
         {
@@ -162,8 +175,12 @@ public class WasmAppBuilder : Task
         }
         FileCopyChecked(MainJS!, Path.Combine(AppDir, "runtime.js"), string.Empty);
 
-        var html = @"<html><body><script type=""text/javascript"" src=""runtime.js""></script></body></html>";
-        File.WriteAllText(Path.Combine(AppDir, "index.html"), html);
+        string indexHtmlPath = Path.Combine(AppDir, "index.html");
+        if (!File.Exists(indexHtmlPath))
+        {
+            var html = @"<html><body><script type=""text/javascript"" src=""runtime.js""></script></body></html>";
+            File.WriteAllText(indexHtmlPath, html);
+        }
 
         foreach (var assembly in _assemblies)
         {
index d7cb1b1..a487a4f 100644 (file)
@@ -15,6 +15,7 @@
 
   <ItemGroup>
     <Compile Include="..\Common\Utils.cs" />
+    <Compile Include="..\Common\LogAsErrorException.cs" />
 
     <PackageReference Include="Microsoft.Build" Version="$(RefOnlyMicrosoftBuildVersion)" />
     <PackageReference Include="Microsoft.Build.Framework" Version="$(RefOnlyMicrosoftBuildFrameworkVersion)" />
diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs
new file mode 100644 (file)
index 0000000..687429a
--- /dev/null
@@ -0,0 +1,194 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Linq;
+using Xunit;
+using Xunit.Abstractions;
+
+#nullable enable
+
+namespace Wasm.Build.Tests
+{
+    public class BlazorWasmBuildPublishTests : BuildTestBase
+    {
+        public BlazorWasmBuildPublishTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
+            : base(output, buildContext)
+        {
+            _enablePerTestCleanup = true;
+        }
+
+        [ConditionalTheory(typeof(BuildTestBase), nameof(IsNotUsingWorkloads))]
+        [InlineData("Debug")]
+        [InlineData("Release")]
+        public void DefaultTemplate_WithoutWorkload(string config)
+        {
+            string id = $"blz_no_workload_{config}";
+            CreateBlazorWasmTemplateProject(id);
+
+            // Build
+            BuildInternal(id, config, publish: false);
+            AssertBlazorBootJson(config, isPublish: false);
+
+            // Publish
+            BuildInternal(id, config, publish: true);
+            AssertBlazorBootJson(config, isPublish: true);
+        }
+
+        [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
+        [InlineData("Debug")]
+        [InlineData("Release")]
+        public void DefaultTemplate_NoAOT_WithWorkload(string config)
+        {
+            string id = $"blz_no_aot_{config}";
+            CreateBlazorWasmTemplateProject(id);
+
+            BlazorBuild(id, config, NativeFilesType.FromRuntimePack);
+            if (config == "Release")
+            {
+                // relinking in publish for Release config
+                BlazorPublish(id, config, NativeFilesType.Relinked);
+            }
+            else
+            {
+                BlazorPublish(id, config, NativeFilesType.FromRuntimePack);
+            }
+        }
+
+        [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
+        [InlineData("Debug")]
+        [InlineData("Release")]
+        public void DefaultTemplate_AOT_InProjectFile(string config)
+        {
+            string id = $"blz_aot_prj_file_{config}";
+            string projectFile = CreateBlazorWasmTemplateProject(id);
+            AddItemsPropertiesToProject(projectFile, extraProperties: "<RunAOTCompilation>true</RunAOTCompilation>");
+
+            // No relinking, no AOT
+            BlazorBuild(id, config, NativeFilesType.FromRuntimePack);
+
+            // will aot
+            BlazorPublish(id, config, NativeFilesType.AOT);
+
+            // build again
+            BlazorBuild(id, config, NativeFilesType.FromRuntimePack);
+        }
+
+        [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
+        [InlineData("Debug", true)]
+        [InlineData("Debug", false)]
+        [InlineData("Release", true)]
+        [InlineData("Release", false)]
+        public void NativeBuild_WithDeployOnBuild_UsedByVS(string config, bool nativeRelink)
+        {
+            string id = $"blz_deploy_on_build_{config}_{nativeRelink}";
+            string projectFile = CreateProjectWithNativeReference(id);
+            AddItemsPropertiesToProject(projectFile, extraProperties: nativeRelink ? string.Empty : "<RunAOTCompilation>true</RunAOTCompilation>");
+
+            // build with -p:DeployOnBuild=true, and that will trigger a publish
+            (CommandResult res, _) = BuildInternal(id, config, publish: false, "-p:DeployOnBuild=true");
+
+            var expectedFileType = nativeRelink ? NativeFilesType.Relinked : NativeFilesType.AOT;
+
+            AssertDotNetNativeFiles(expectedFileType, config, forPublish: true);
+            AssertBlazorBundle(config, isPublish: true, dotnetWasmFromRuntimePack: false);
+
+            if (expectedFileType == NativeFilesType.AOT)
+            {
+                // check for this too, so we know the format is correct for the negative
+                // test for jsinterop.webassembly.dll
+                Assert.Contains("Microsoft.JSInterop.dll -> Microsoft.JSInterop.dll.bc", res.Output);
+
+                // make sure this assembly gets skipped
+                Assert.DoesNotContain("Microsoft.JSInterop.WebAssembly.dll -> Microsoft.JSInterop.WebAssembly.dll.bc", res.Output);
+            }
+
+            // Check that we linked only for publish
+            string objBuildDir = Path.Combine(_projectDir!, "obj", config, "net6.0", "wasm", "for-build");
+            Assert.False(Directory.Exists(objBuildDir), $"Found unexpected {objBuildDir}, which gets creating when relinking during Build");
+
+            // double check!
+            int index = res.Output.IndexOf("pinvoke.c -> pinvoke.o");
+            Assert.NotEqual(-1, index);
+
+            // there should be only one instance of this string!
+            index = res.Output.IndexOf("pinvoke.c -> pinvoke.o", index + 1);
+            Assert.Equal(-1, index);
+        }
+
+        // Disabling for now - publish folder can have more than one dotnet*hash*js, and not sure
+        // how to pick which one to check, for the test
+        //[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
+        //[InlineData("Debug")]
+        //[InlineData("Release")]
+        //public void DefaultTemplate_AOT_OnlyWithPublishCommandLine_Then_PublishNoAOT(string config)
+        //{
+            //string id = $"blz_aot_pub_{config}";
+            //CreateBlazorWasmTemplateProject(id);
+
+            //// No relinking, no AOT
+            //BlazorBuild(id, config, NativeFilesType.FromRuntimePack);
+
+            //// AOT=true only for the publish command line, similar to what
+            //// would happen when setting it in Publish dialog for VS
+            //BlazorPublish(id, config, expectedFileType: NativeFilesType.AOT, "-p:RunAOTCompilation=true");
+
+            //// publish again, no AOT
+            //BlazorPublish(id, config, NativeFilesType.Relinked);
+        //}
+
+        [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
+        [InlineData("Debug")]
+        [InlineData("Release")]
+        public void WithNativeReference_AOTInProjectFile(string config)
+        {
+            string id = $"blz_nativeref_aot_{config}";
+            string projectFile = CreateProjectWithNativeReference(id);
+            AddItemsPropertiesToProject(projectFile, extraProperties: "<RunAOTCompilation>true</RunAOTCompilation>");
+
+            BlazorBuild(id, config, expectedFileType: NativeFilesType.Relinked);
+
+            BlazorPublish(id, config, expectedFileType: NativeFilesType.AOT);
+
+            // will relink
+            BlazorBuild(id, config, expectedFileType: NativeFilesType.Relinked);
+        }
+
+        [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
+        [InlineData("Debug")]
+        [InlineData("Release")]
+        public void WithNativeReference_AOTOnCommandLine(string config)
+        {
+            string id = $"blz_nativeref_aot_{config}";
+            CreateProjectWithNativeReference(id);
+
+            BlazorBuild(id, config, expectedFileType: NativeFilesType.Relinked);
+
+            BlazorPublish(id, config, expectedFileType: NativeFilesType.AOT, "-p:RunAOTCompilation=true");
+
+            // no aot!
+            BlazorPublish(id, config, expectedFileType: NativeFilesType.Relinked);
+        }
+
+        private string CreateProjectWithNativeReference(string id)
+        {
+            CreateBlazorWasmTemplateProject(id);
+
+            string extraItems = @$"
+                <PackageReference Include=""SkiaSharp"" Version=""2.80.3"" />
+                <PackageReference Include=""SkiaSharp.NativeAssets.WebAssembly"" Version=""2.80.3"" />
+
+                <NativeFileReference Include=""$(SkiaSharpStaticLibraryPath)\2.0.9\*.a"" />
+                <WasmFilesToIncludeInFileSystem Include=""{Path.Combine(BuildEnvironment.TestAssetsPath, "mono.png")}"" />
+            ";
+            string projectFile = Path.Combine(_projectDir!, $"{id}.csproj");
+            AddItemsPropertiesToProject(projectFile, extraItems: extraItems);
+
+            return projectFile;
+        }
+
+    }
+
+    public enum NativeFilesType { FromRuntimePack, Relinked, AOT };
+}
index 9b77090..6c7e3ed 100644 (file)
@@ -1,7 +1,6 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System.Collections.Generic;
 using System.IO;
 using Xunit;
 using Xunit.Abstractions;
@@ -17,36 +16,6 @@ namespace Wasm.Build.Tests
         {
         }
 
-        // TODO: invariant case?
-
-        [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
-        [InlineData("Debug", false)]
-        [InlineData("Debug", true)] // just aot
-        [InlineData("Release", false)] // should re-link
-        [InlineData("Release", true)]
-        public void PublishTemplateProject(string config, bool aot)
-        {
-            string id = $"blazorwasm_{config}_aot_{aot}_{Path.GetRandomFileName()}";
-            InitBlazorWasmProjectDir(id);
-
-            new DotNetCommand(s_buildEnv, useDefaultArgs: false)
-                    .WithWorkingDirectory(_projectDir!)
-                    .ExecuteWithCapturedOutput("new blazorwasm")
-                    .EnsureSuccessful();
-
-            string publishLogPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}.binlog");
-            new DotNetCommand(s_buildEnv)
-                    .WithWorkingDirectory(_projectDir!)
-                    .ExecuteWithCapturedOutput("publish", $"-bl:{publishLogPath}", aot ? "-p:RunAOTCompilation=true" : "", $"-p:Configuration={config}")
-                    .EnsureSuccessful();
-
-            //TODO: validate the build somehow?
-            // compare dotnet.wasm?
-            // relinking - dotnet.wasm should be smaller
-            //
-            // playwright?
-        }
-
         [ConditionalTheory(typeof(BuildTestBase), nameof(IsNotUsingWorkloads))]
         [InlineData("Debug")]
         [InlineData("Release")]
@@ -54,6 +23,7 @@ namespace Wasm.Build.Tests
         {
             CommandResult res = PublishForRequiresWorkloadTest(config, extraItems: "<NativeFileReference Include=\"native-lib.o\" />");
             res.EnsureSuccessful();
+            AssertBlazorBundle(config, isPublish: true, dotnetWasmFromRuntimePack: true);
 
             Assert.Contains("but the native references won't be linked in", res.Output);
         }
@@ -71,7 +41,7 @@ namespace Wasm.Build.Tests
         [ConditionalTheory(typeof(BuildTestBase), nameof(IsNotUsingWorkloads))]
         [InlineData("Debug")]
         [InlineData("Release")]
-        public void AOT_And_NativeRef_FailsBecauseItRequireWorkload(string config)
+        public void AOT_And_NativeRef_FailBecauseTheyRequireWorkload(string config)
         {
             CommandResult res = PublishForRequiresWorkloadTest(config,
                                     extraProperties: "<RunAOTCompilation>true</RunAOTCompilation>",
@@ -84,18 +54,13 @@ namespace Wasm.Build.Tests
         private CommandResult PublishForRequiresWorkloadTest(string config, string extraItems="", string extraProperties="")
         {
             string id = $"needs_workload_{config}_{Path.GetRandomFileName()}";
-            InitBlazorWasmProjectDir(id);
-
-            new DotNetCommand(s_buildEnv, useDefaultArgs: false)
-                    .WithWorkingDirectory(_projectDir!)
-                    .ExecuteWithCapturedOutput("new blazorwasm")
-                    .EnsureSuccessful();
+            CreateBlazorWasmTemplateProject(id);
 
             if (IsNotUsingWorkloads)
             {
                 // no packs installed, so no need to update the paths for runtime pack etc
-                File.WriteAllText(Path.Combine(_projectDir!, "Directory.Build.props"), "<Project />");
-                File.WriteAllText(Path.Combine(_projectDir!, "Directory.Build.targets"), "<Project />");
+                File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Local.Directory.Build.props"), Path.Combine(_projectDir!, "Directory.Build.props"), overwrite: true);
+                File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Local.Directory.Build.targets"), Path.Combine(_projectDir!, "Directory.Build.targets"), overwrite: true);
             }
 
             AddItemsPropertiesToProject(Path.Combine(_projectDir!, $"{id}.csproj"),
@@ -136,7 +101,7 @@ namespace Wasm.Build.Tests
             InitBlazorWasmProjectDir(id);
 
             string directoryBuildTargets = @"<Project>
-                <Target Name=""PrintAllProjects"" BeforeTargets=""Build"">
+            <Target Name=""PrintAllProjects"" BeforeTargets=""Build"">
                     <Message Text=""** UsingBrowserRuntimeWorkload: '$(UsingBrowserRuntimeWorkload)'"" Importance=""High"" />
                 </Target>
             </Project>";
@@ -152,11 +117,11 @@ namespace Wasm.Build.Tests
 
             string publishLogPath = Path.Combine(logPath, $"{id}.binlog");
             CommandResult result = new DotNetCommand(s_buildEnv)
-                            .WithWorkingDirectory(_projectDir!)
-                            .ExecuteWithCapturedOutput("publish",
-                                                       $"-bl:{publishLogPath}",
-                                                       (aot ? "-p:RunAOTCompilation=true" : ""),
-                                                       $"-p:Configuration={config}");
+                                            .WithWorkingDirectory(_projectDir!)
+                                            .ExecuteWithCapturedOutput("publish",
+                                                                       $"-bl:{publishLogPath}",
+                                                                       (aot ? "-p:RunAOTCompilation=true" : ""),
+                                                                       $"-p:Configuration={config}");
 
             if (expectError)
             {
@@ -167,6 +132,11 @@ namespace Wasm.Build.Tests
             {
                 result.EnsureSuccessful();
                 Assert.Contains("** UsingBrowserRuntimeWorkload: 'false'", result.Output);
+
+                string binFrameworkDir = FindBlazorBinFrameworkDir(config, forPublish: true, framework: "net5.0");
+                AssertBlazorBootJson(config, isPublish: true, binFrameworkDir: binFrameworkDir);
+                // dotnet.wasm here would be from 5.0 nuget like:
+                // /Users/radical/.nuget/packages/microsoft.netcore.app.runtime.browser-wasm/5.0.9/runtimes/browser-wasm/native/dotnet.wasm
             }
         }
     }
index c49b195..8d66153 100644 (file)
@@ -34,9 +34,9 @@ namespace Wasm.Build.Tests
                     .UnwrapItemsAsArrays().ToList().Dump();
         }
 
-        public BuildAndRunAttribute(bool aot=false, RunHost host = RunHost.All, params object?[] parameters)
+        public BuildAndRunAttribute(bool aot=false, RunHost host = RunHost.All, string? config=null, params object?[] parameters)
         {
-            _data = BuildTestBase.ConfigWithAOTData(aot)
+            _data = BuildTestBase.ConfigWithAOTData(aot, config)
                     .Multiply(parameters)
                     .WithRunHosts(host)
                     .UnwrapItemsAsArrays().ToList().Dump();
index 8e9be9e..b1de19b 100644 (file)
@@ -135,5 +135,8 @@ namespace Wasm.Build.Tests
 
         protected static string s_directoryBuildPropsForLocal = File.ReadAllText(Path.Combine(TestDataPath, "Local.Directory.Build.props"));
         protected static string s_directoryBuildTargetsForLocal = File.ReadAllText(Path.Combine(TestDataPath, "Local.Directory.Build.targets"));
+
+        protected static string s_directoryBuildPropsForBlazorLocal = File.ReadAllText(Path.Combine(TestDataPath, "Blazor.Local.Directory.Build.props"));
+        protected static string s_directoryBuildTargetsForBlazorLocal = File.ReadAllText(Path.Combine(TestDataPath, "Blazor.Local.Directory.Build.targets"));
     }
 }
diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildPublishTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildPublishTests.cs
new file mode 100644 (file)
index 0000000..42eb966
--- /dev/null
@@ -0,0 +1,158 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Linq;
+using Wasm.Build.NativeRebuild.Tests;
+using Xunit;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+#nullable enable
+
+namespace Wasm.Build.Tests
+{
+    public class BuildPublishTests : NativeRebuildTestsBase
+    {
+        public BuildPublishTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
+            : base(output, buildContext)
+        {
+        }
+
+        [Theory]
+        [BuildAndRun(host: RunHost.V8, aot: false, config: "Release")]
+        [BuildAndRun(host: RunHost.V8, aot: false, config: "Debug")]
+        public void BuildThenPublishNoAOT(BuildArgs buildArgs, RunHost host, string id)
+        {
+            string projectName = $"build_publish_{buildArgs.Config}";
+
+            buildArgs = buildArgs with { ProjectName = projectName };
+            buildArgs = ExpandBuildArgs(buildArgs);
+
+            // no relinking for build
+            bool relinked = false;
+            BuildProject(buildArgs,
+                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                        dotnetWasmFromRuntimePack: !relinked,
+                        id: id,
+                        createProject: true,
+                        publish: false);
+
+            Run();
+
+            if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product))
+                throw new XunitException($"Test bug: could not get the build product in the cache");
+
+            File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog"));
+
+            _testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}");
+            Console.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}");
+
+            // relink by default for Release+publish
+            relinked = buildArgs.Config == "Release";
+            BuildProject(buildArgs,
+                        id: id,
+                        dotnetWasmFromRuntimePack: !relinked,
+                        createProject: false,
+                        publish: true,
+                        useCache: false);
+
+            Run();
+
+            void Run() => RunAndTestWasmApp(
+                                buildArgs, buildDir: _projectDir, expectedExitCode: 42,
+                                test: output => {},
+                                host: host, id: id);
+        }
+
+        [Theory]
+        [BuildAndRun(host: RunHost.V8, aot: true, config: "Release")]
+        [BuildAndRun(host: RunHost.V8, aot: true, config: "Debug")]
+        public void BuildThenPublishWithAOT(BuildArgs buildArgs, RunHost host, string id)
+        {
+            string projectName = $"build_publish_{buildArgs.Config}";
+
+            buildArgs = buildArgs with { ProjectName = projectName };
+            buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "<_WasmDevel>true</_WasmDevel>");
+
+            // no relinking for build
+            bool relinked = false;
+            (_, string output) = BuildProject(buildArgs,
+                                    initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                                    dotnetWasmFromRuntimePack: !relinked,
+                                    id: id,
+                                    createProject: true,
+                                    publish: false,
+                                    label: "first_build");
+
+            BuildPaths paths = GetBuildPaths(buildArgs);
+            var pathsDict = GetFilesTable(buildArgs, paths, unchanged: false);
+
+            string mainDll = $"{buildArgs.ProjectName}.dll";
+            var firstBuildStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath));
+            Assert.False(firstBuildStat["pinvoke.o"].Exists);
+            Assert.False(firstBuildStat[$"{mainDll}.bc"].Exists);
+
+            CheckOutputForNativeBuild(expectAOT: false, expectRelinking: relinked, buildArgs, output);
+
+            Run(expectAOT: false);
+
+            if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product))
+                throw new XunitException($"Test bug: could not get the build product in the cache");
+
+            File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog"));
+
+            _testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}");
+            Console.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}");
+
+            // relink by default for Release+publish
+            (_, output) = BuildProject(buildArgs,
+                                    id: id,
+                                    dotnetWasmFromRuntimePack: false,
+                                    createProject: false,
+                                    publish: true,
+                                    useCache: false,
+                                    label: "first_publish");
+
+            var publishStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath));
+            Assert.True(publishStat["pinvoke.o"].Exists);
+            Assert.True(publishStat[$"{mainDll}.bc"].Exists);
+            CheckOutputForNativeBuild(expectAOT: true, expectRelinking: false, buildArgs, output);
+            CompareStat(firstBuildStat, publishStat, pathsDict.Values);
+
+            Run(expectAOT: true);
+
+            // second build
+            (_, output) = BuildProject(buildArgs,
+                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                        dotnetWasmFromRuntimePack: !relinked,
+                        id: id,
+                        createProject: true,
+                        publish: false,
+                        label: "second_build");
+            var secondBuildStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath));
+
+            // no relinking, or AOT
+            CheckOutputForNativeBuild(expectAOT: false, expectRelinking: false, buildArgs, output);
+
+            // no native files changed
+            pathsDict.UpdateTo(unchanged: true);
+            CompareStat(publishStat, secondBuildStat, pathsDict.Values);
+
+            void Run(bool expectAOT) => RunAndTestWasmApp(
+                                buildArgs with { AOT = expectAOT },
+                                buildDir: _projectDir, expectedExitCode: 42,
+                                host: host, id: id);
+        }
+
+        void CheckOutputForNativeBuild(bool expectAOT, bool expectRelinking, BuildArgs buildArgs, string buildOutput)
+        {
+            AssertSubstring($"{buildArgs.ProjectName}.dll -> {buildArgs.ProjectName}.dll.bc", buildOutput, expectAOT);
+            AssertSubstring($"{buildArgs.ProjectName}.dll.bc -> {buildArgs.ProjectName}.dll.o", buildOutput, expectAOT);
+
+            AssertSubstring("pinvoke.c -> pinvoke.o", buildOutput, expectRelinking || expectAOT);
+        }
+
+    }
+}
index 50425f9..b6fab5c 100644 (file)
@@ -10,6 +10,7 @@ using System.IO;
 using System.Linq;
 using System.Runtime.InteropServices;
 using System.Text;
+using System.Text.Json.Nodes;
 using System.Text.RegularExpressions;
 using System.Xml;
 using Xunit;
@@ -285,8 +286,11 @@ namespace Wasm.Build.Tests
                                   bool useCache = true,
                                   bool expectSuccess = true,
                                   bool createProject = true,
-                                  string? verbosity=null)
+                                  bool publish = true,
+                                  string? verbosity=null,
+                                  string? label=null)
         {
+            string msgPrefix = label != null ? $"[{label}] " : string.Empty;
             if (useCache && _buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product))
             {
                 Console.WriteLine ($"Using existing build found at {product.ProjectDir}, with build log at {product.LogFile}");
@@ -314,12 +318,13 @@ namespace Wasm.Build.Tests
             }
 
             StringBuilder sb = new();
-            sb.Append("publish");
+            sb.Append(publish ? "publish" : "build");
             sb.Append($" {s_buildEnv.DefaultBuildArgs}");
 
             sb.Append($" /p:Configuration={buildArgs.Config}");
 
-            string logFilePath = Path.Combine(_logPath, $"{buildArgs.ProjectName}.binlog");
+            string logFileSuffix = label == null ? string.Empty : label.Replace(' ', '_');
+            string logFilePath = Path.Combine(_logPath, $"{buildArgs.ProjectName}{logFileSuffix}.binlog");
             _testOutput.WriteLine($"-------- Building ---------");
             _testOutput.WriteLine($"Binlog path: {logFilePath}");
             Console.WriteLine($"Binlog path: {logFilePath}");
@@ -371,6 +376,100 @@ namespace Wasm.Build.Tests
             File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Directory.Build.targets"), Path.Combine(_projectDir, "Directory.Build.targets"));
         }
 
+        public string CreateBlazorWasmTemplateProject(string id)
+        {
+            InitBlazorWasmProjectDir(id);
+            new DotNetCommand(s_buildEnv, useDefaultArgs: false)
+                    .WithWorkingDirectory(_projectDir!)
+                    .ExecuteWithCapturedOutput("new blazorwasm")
+                    .EnsureSuccessful();
+
+            return Path.Combine(_projectDir!, $"{id}.csproj");
+        }
+
+        protected (CommandResult, string) BlazorBuild(string id, string config, NativeFilesType expectedFileType, params string[] extraArgs)
+        {
+            var res = BuildInternal(id, config, publish: false, extraArgs);
+            AssertDotNetNativeFiles(expectedFileType, config, forPublish: false);
+            AssertBlazorBundle(config, isPublish: false, dotnetWasmFromRuntimePack: expectedFileType == NativeFilesType.FromRuntimePack);
+
+            return res;
+        }
+
+        protected (CommandResult, string) BlazorPublish(string id, string config, NativeFilesType expectedFileType, params string[] extraArgs)
+        {
+            var res = BuildInternal(id, config, publish: true, extraArgs);
+            AssertDotNetNativeFiles(expectedFileType, config, forPublish: true);
+            AssertBlazorBundle(config, isPublish: true, dotnetWasmFromRuntimePack: expectedFileType == NativeFilesType.FromRuntimePack);
+
+            if (expectedFileType == NativeFilesType.AOT)
+            {
+                // check for this too, so we know the format is correct for the negative
+                // test for jsinterop.webassembly.dll
+                Assert.Contains("Microsoft.JSInterop.dll -> Microsoft.JSInterop.dll.bc", res.Item1.Output);
+
+                // make sure this assembly gets skipped
+                Assert.DoesNotContain("Microsoft.JSInterop.WebAssembly.dll -> Microsoft.JSInterop.WebAssembly.dll.bc", res.Item1.Output);
+            }
+            return res;
+        }
+
+        protected (CommandResult, string) BuildInternal(string id, string config, bool publish=false, params string[] extraArgs)
+        {
+            string label = publish ? "publish" : "build";
+            Console.WriteLine($"{Environment.NewLine}** {label} **{Environment.NewLine}");
+
+            string logPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-{label}.binlog");
+            string[] combinedArgs = new[]
+            {
+                label, // same as the command name
+                $"-bl:{logPath}",
+                $"-p:Configuration={config}",
+                "-p:BlazorEnableCompression=false",
+                "-p:_WasmDevel=true"
+            }.Concat(extraArgs).ToArray();
+
+            CommandResult res = new DotNetCommand(s_buildEnv)
+                                        .WithWorkingDirectory(_projectDir!)
+                                        .ExecuteWithCapturedOutput(combinedArgs)
+                                        .EnsureSuccessful();
+
+            return (res, logPath);
+        }
+
+        protected void AssertDotNetNativeFiles(NativeFilesType type, string config, bool forPublish)
+        {
+            string label = forPublish ? "publish" : "build";
+            string objBuildDir = Path.Combine(_projectDir!, "obj", config, "net6.0", "wasm", forPublish ? "for-publish" : "for-build");
+            string binFrameworkDir = FindBlazorBinFrameworkDir(config, forPublish);
+
+            string srcDir = type switch
+            {
+                NativeFilesType.FromRuntimePack => s_buildEnv.RuntimeNativeDir,
+                NativeFilesType.Relinked => objBuildDir,
+                NativeFilesType.AOT => objBuildDir,
+                _ => throw new ArgumentOutOfRangeException(nameof(type))
+            };
+
+            AssertSameFile(Path.Combine(srcDir, "dotnet.wasm"), Path.Combine(binFrameworkDir, "dotnet.wasm"), label);
+
+            // find dotnet*js
+            string? dotnetJsPath = Directory.EnumerateFiles(binFrameworkDir)
+                                    .Where(p => Path.GetFileName(p).StartsWith("dotnet.", StringComparison.OrdinalIgnoreCase) &&
+                                                    Path.GetFileName(p).EndsWith(".js", StringComparison.OrdinalIgnoreCase))
+                                    .SingleOrDefault();
+
+            Assert.True(!string.IsNullOrEmpty(dotnetJsPath), $"[{label}] Expected to find dotnet*js in {binFrameworkDir}");
+            AssertSameFile(Path.Combine(srcDir, "dotnet.js"), dotnetJsPath!, label);
+
+            if (type != NativeFilesType.FromRuntimePack)
+            {
+                // check that the files are *not* from runtime pack
+                AssertNotSameFile(Path.Combine(s_buildEnv.RuntimeNativeDir, "dotnet.wasm"), Path.Combine(binFrameworkDir, "dotnet.wasm"), label);
+                AssertNotSameFile(Path.Combine(s_buildEnv.RuntimeNativeDir, "dotnet.js"), dotnetJsPath!, label);
+            }
+        }
+
         static void AssertRuntimePackPath(string buildOutput)
         {
             var match = s_runtimePackPathRegex.Match(buildOutput);
@@ -384,7 +483,6 @@ namespace Wasm.Build.Tests
 
         protected static void AssertBasicAppBundle(string bundleDir, string projectName, string config, bool hasIcudt=true, bool dotnetWasmFromRuntimePack=true)
         {
-            Console.WriteLine ($"AssertBasicAppBundle: {dotnetWasmFromRuntimePack}");
             AssertFilesExist(bundleDir, new []
             {
                 "index.html",
@@ -486,6 +584,66 @@ namespace Wasm.Build.Tests
             return result;
         }
 
+        protected void AssertBlazorBundle(string config, bool isPublish, bool dotnetWasmFromRuntimePack, string? binFrameworkDir=null)
+        {
+            binFrameworkDir ??= FindBlazorBinFrameworkDir(config, isPublish);
+
+            AssertBlazorBootJson(config, isPublish, binFrameworkDir: binFrameworkDir);
+            AssertFile(Path.Combine(s_buildEnv.RuntimeNativeDir, "dotnet.wasm"),
+                       Path.Combine(binFrameworkDir, "dotnet.wasm"),
+                       "Expected dotnet.wasm to be same as the runtime pack",
+                       same: dotnetWasmFromRuntimePack);
+
+            string? dotnetJsPath = Directory.EnumerateFiles(binFrameworkDir, "dotnet.*.js").FirstOrDefault();
+            Assert.True(dotnetJsPath != null, $"Could not find blazor's dotnet*js in {binFrameworkDir}");
+
+            AssertFile(Path.Combine(s_buildEnv.RuntimeNativeDir, "dotnet.js"),
+                        dotnetJsPath!,
+                        "Expected dotnet.js to be same as the runtime pack",
+                        same: dotnetWasmFromRuntimePack);
+        }
+
+        protected void AssertBlazorBootJson(string config, bool isPublish, string? binFrameworkDir=null)
+        {
+            binFrameworkDir ??= FindBlazorBinFrameworkDir(config, isPublish);
+
+            string bootJsonPath = Path.Combine(binFrameworkDir, "blazor.boot.json");
+            Assert.True(File.Exists(bootJsonPath), $"Expected to find {bootJsonPath}");
+
+            string bootJson = File.ReadAllText(bootJsonPath);
+            var bootJsonNode = JsonNode.Parse(bootJson);
+            var runtimeObj = bootJsonNode?["resources"]?["runtime"]?.AsObject();
+            Assert.NotNull(runtimeObj);
+
+            string msgPrefix=$"[{( isPublish ? "publish" : "build" )}]";
+            Assert.True(runtimeObj!.Where(kvp => kvp.Key == "dotnet.wasm").Any(), $"{msgPrefix} Could not find dotnet.wasm entry in blazor.boot.json");
+            Assert.True(runtimeObj!.Where(kvp => kvp.Key.StartsWith("dotnet.", StringComparison.OrdinalIgnoreCase) &&
+                                                    kvp.Key.EndsWith(".js", StringComparison.OrdinalIgnoreCase)).Any(),
+                                            $"{msgPrefix} Could not find dotnet.*js in {bootJson}");
+        }
+
+        protected string FindBlazorBinFrameworkDir(string config, bool forPublish, string framework="net6.0")
+        {
+            string basePath = Path.Combine(_projectDir!, "bin", config, framework);
+            if (forPublish)
+                basePath = FindSubDirIgnoringCase(basePath, "publish");
+
+            return Path.Combine(basePath, "wwwroot", "_framework");
+        }
+
+        private string FindSubDirIgnoringCase(string parentDir, string dirName)
+        {
+            IEnumerable<string> matchingDirs = Directory.EnumerateDirectories(parentDir,
+                                                            dirName,
+                                                            new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive });
+
+            string? first = matchingDirs.FirstOrDefault();
+            if (matchingDirs.Count() > 1)
+                throw new Exception($"Found multiple directories with names that differ only in case. {string.Join(", ", matchingDirs.ToArray())}");
+
+            return first ?? Path.Combine(parentDir, dirName);
+        }
+
         protected string GetBinDir(string config, string targetFramework=s_targetFramework, string? baseDir=null)
         {
             var dir = baseDir ?? _projectDir;
@@ -615,26 +773,34 @@ namespace Wasm.Build.Tests
             }
         }
 
-        public static string AddItemsPropertiesToProject(string projectFile, string? extraProperties=null, string? extraItems=null)
+        public static string AddItemsPropertiesToProject(string projectFile, string? extraProperties=null, string? extraItems=null, string? atTheEnd=null)
         {
-            if (extraProperties == null && extraItems == null)
+            if (extraProperties == null && extraItems == null && atTheEnd == null)
                 return projectFile;
 
             XmlDocument doc = new();
             doc.Load(projectFile);
 
+            XmlNode root = doc.DocumentElement ?? throw new Exception();
             if (extraItems != null)
             {
                 XmlNode node = doc.CreateNode(XmlNodeType.Element, "ItemGroup", null);
                 node.InnerXml = extraItems;
-                doc.DocumentElement!.AppendChild(node);
+                root.AppendChild(node);
             }
 
             if (extraProperties != null)
             {
                 XmlNode node = doc.CreateNode(XmlNodeType.Element, "PropertyGroup", null);
                 node.InnerXml = extraProperties;
-                doc.DocumentElement!.AppendChild(node);
+                root.AppendChild(node);
+            }
+
+            if (atTheEnd != null)
+            {
+                XmlNode node = doc.CreateNode(XmlNodeType.DocumentFragment, "foo", null);
+                node.InnerXml = atTheEnd;
+                root.InsertAfter(node, root.LastChild);
             }
 
             doc.Save(projectFile);
@@ -654,6 +820,29 @@ namespace Wasm.Build.Tests
             return string.IsNullOrEmpty(value) ? defaultValue : value;
         }
 
+        internal BuildPaths GetBuildPaths(BuildArgs buildArgs, bool forPublish=true)
+        {
+            string objDir = GetObjDir(buildArgs.Config);
+            string bundleDir = Path.Combine(GetBinDir(baseDir: _projectDir, config: buildArgs.Config), "AppBundle");
+            string wasmDir = Path.Combine(objDir, "wasm", forPublish ? "for-publish" : "for-build");
+
+            return new BuildPaths(wasmDir, objDir, GetBinDir(buildArgs.Config), bundleDir);
+        }
+
+        internal IDictionary<string, FileStat> StatFiles(IEnumerable<string> fullpaths)
+        {
+            Dictionary<string, FileStat> table = new();
+            foreach (string file in fullpaths)
+            {
+                if (File.Exists(file))
+                    table.Add(Path.GetFileName(file), new FileStat(FullPath: file, Exists: true, LastWriteTimeUtc: File.GetLastWriteTimeUtc(file), Length: new FileInfo(file).Length));
+                else
+                    table.Add(Path.GetFileName(file), new FileStat(FullPath: file, Exists: false, LastWriteTimeUtc: DateTime.MinValue, Length: 0));
+            }
+
+            return table;
+        }
+
         protected static string s_mainReturns42 = @"
             public class TestClass {
                 public static int Main()
@@ -669,4 +858,6 @@ namespace Wasm.Build.Tests
                             string ProjectFileContents,
                             string? ExtraBuildArgs);
     public record BuildProduct(string ProjectDir, string LogFile, bool Result);
+    internal record FileStat (bool Exists, DateTime LastWriteTimeUtc, long Length, string FullPath);
+    internal record BuildPaths(string ObjWasmDir, string ObjDir, string BinDir, string BundleDir);
  }
diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/CleanTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/CleanTests.cs
new file mode 100644 (file)
index 0000000..d2226d5
--- /dev/null
@@ -0,0 +1,110 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Linq;
+using Wasm.Build.NativeRebuild.Tests;
+using Xunit;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+#nullable enable
+
+namespace Wasm.Build.Tests;
+
+public class CleanTests : NativeRebuildTestsBase
+{
+    public CleanTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
+        : base(output, buildContext)
+    {
+    }
+
+    [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
+    [InlineData("Debug")]
+    [InlineData("Release")]
+    public void Blazor_BuildThenClean_NativeRelinking(string config)
+    {
+        string id = Path.GetRandomFileName();
+
+        InitBlazorWasmProjectDir(id);
+        string projectFile = CreateBlazorWasmTemplateProject(id);
+
+        string extraProperties = @"<_WasmDevel>true</_WasmDevel>
+                                    <WasmBuildNative>true</WasmBuildNative>";
+
+        AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties);
+        BlazorBuild(id, config, NativeFilesType.Relinked);
+
+        string relinkDir = Path.Combine(_projectDir!, "obj", config, "net6.0", "wasm", "for-build");
+        Assert.True(Directory.Exists(relinkDir), $"Could not find expected relink dir: {relinkDir}");
+
+        string logPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-clean.binlog");
+        new DotNetCommand(s_buildEnv)
+                .WithWorkingDirectory(_projectDir!)
+                .ExecuteWithCapturedOutput("build", "-t:Clean", $"-p:Configuration={config}", $"-bl:{logPath}")
+                .EnsureSuccessful();
+
+        AssertEmptyOrNonExistantDirectory(relinkDir);
+    }
+
+    [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
+    [InlineData("Debug")]
+    [InlineData("Release")]
+    public void Blazor_BuildNoNative_ThenBuildNative_ThenClean(string config)
+        => Blazor_BuildNativeNonNative_ThenCleanTest(config, firstBuildNative: false);
+
+    [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
+    [InlineData("Debug")]
+    [InlineData("Release")]
+    public void Blazor_BuildNative_ThenBuildNonNative_ThenClean(string config)
+        => Blazor_BuildNativeNonNative_ThenCleanTest(config, firstBuildNative: true);
+
+    private void Blazor_BuildNativeNonNative_ThenCleanTest(string config, bool firstBuildNative)
+    {
+        string id = Path.GetRandomFileName();
+
+        InitBlazorWasmProjectDir(id);
+        string projectFile = CreateBlazorWasmTemplateProject(id);
+
+        string extraProperties = @"<_WasmDevel>true</_WasmDevel>";
+
+        AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties);
+
+        bool relink = firstBuildNative;
+        BuildInternal(id, config, publish: false,
+                        extraArgs: relink ? "-p:WasmBuildNative=true" : string.Empty);
+
+        string relinkDir = Path.Combine(_projectDir!, "obj", config, "net6.0", "wasm", "for-build");
+        if (relink)
+            Assert.True(Directory.Exists(relinkDir), $"Could not find expected relink dir: {relinkDir}");
+
+        relink = !firstBuildNative;
+        BuildInternal(id, config, publish: false,
+                        extraArgs: relink ? "-p:WasmBuildNative=true" : string.Empty);
+
+        if (relink)
+            Assert.True(Directory.Exists(relinkDir), $"Could not find expected relink dir: {relinkDir}");
+
+        string logPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-clean.binlog");
+        new DotNetCommand(s_buildEnv)
+                .WithWorkingDirectory(_projectDir!)
+                .ExecuteWithCapturedOutput("build", "-t:Clean", $"-p:Configuration={config}", $"-bl:{logPath}")
+                .EnsureSuccessful();
+
+        AssertEmptyOrNonExistantDirectory(relinkDir);
+    }
+    private void AssertEmptyOrNonExistantDirectory(string dirPath)
+    {
+        Console.WriteLine($"dirPath: {dirPath}");
+        if (!Directory.Exists(dirPath))
+            return;
+
+        var files = Directory.GetFileSystemEntries(dirPath);
+        if (files.Length == 0)
+            return;
+
+        string found = string.Join(',', files.Select(p => Path.GetFileName(p)));
+        throw new XunitException($"Expected dir {dirPath} to be empty, but found: {found}");
+    }
+}
index db95f8b..7372ef0 100644 (file)
@@ -24,10 +24,10 @@ namespace Wasm.Build.Tests
             Output = output;
         }
 
-        public void EnsureSuccessful(string messagePrefix = "", bool suppressOutput = false)
+        public CommandResult EnsureSuccessful(string messagePrefix = "", bool suppressOutput = false)
             => EnsureExitCode(0, messagePrefix, suppressOutput);
 
-        public void EnsureExitCode(int expectedExitCode = 0, string messagePrefix = "", bool suppressOutput = false)
+        public CommandResult EnsureExitCode(int expectedExitCode = 0, string messagePrefix = "", bool suppressOutput = false)
         {
             if (ExitCode != expectedExitCode)
             {
@@ -43,6 +43,8 @@ namespace Wasm.Build.Tests
 
                 throw new XunitException(message.ToString());
             }
+
+            return this;
         }
     }
 }
index a70a51b..bff462f 100644 (file)
@@ -6,6 +6,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.IO;
 using System.Text;
+using System.Collections;
 
 #nullable enable
 
@@ -96,7 +97,9 @@ namespace Wasm.Build.Tests
 
         public static void UpdateTo(this IDictionary<string, (string fullPath, bool unchanged)> dict, bool unchanged, params string[] filenames)
         {
-            foreach (var filename in filenames)
+            IEnumerable<string> keys = filenames.Length == 0 ? dict.Keys.ToList() : filenames;
+
+            foreach (var filename in keys)
             {
                 if (!dict.TryGetValue(filename, out var oldValue))
                 {
index 80c7ba5..3453f99 100644 (file)
@@ -25,17 +25,14 @@ namespace Wasm.Build.Tests
         [Theory]
         [BuildAndRun]
         public void SimpleNativeBuild(BuildArgs buildArgs, RunHost host, string id)
-            => NativeBuild("simple_native_build", s_mainReturns42, buildArgs, host, id);
-
-        private void NativeBuild(string projectNamePrefix, string projectContents, BuildArgs buildArgs, RunHost host, string id)
         {
-            string projectName = $"{projectNamePrefix}_{buildArgs.Config}_{buildArgs.AOT}";
+            string projectName = $"simple_native_build_{buildArgs.Config}_{buildArgs.AOT}";
 
             buildArgs = buildArgs with { ProjectName = projectName };
             buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "<WasmBuildNative>true</WasmBuildNative>");
 
             BuildProject(buildArgs,
-                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), projectContents),
+                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
                         dotnetWasmFromRuntimePack: false,
                         id: id);
 
@@ -45,20 +42,45 @@ namespace Wasm.Build.Tests
         }
 
         [Theory]
+        [BuildAndRun(aot: true, host: RunHost.None)]
+        public void MonoAOTCross_WorksWithNoTrimming(BuildArgs buildArgs, string id)
+        {
+            // stop once `mono-aot-cross` part of the build is done
+            string target = @"<Target Name=""StopAfterWasmAOT"" AfterTargets=""_WasmAotCompileApp"">
+                <Error Text=""Stopping after AOT"" Condition=""'$(WasmBuildingForNestedPublish)' == 'true'"" />
+            </Target>";
+
+            string projectName = $"mono_aot_cross_{buildArgs.Config}_{buildArgs.AOT}";
+
+            buildArgs = buildArgs with { ProjectName = projectName, ExtraBuildArgs = "-p:PublishTrimmed=false -v:n" };
+            buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "<WasmBuildNative>true</WasmBuildNative>", insertAtEnd: target);
+
+            (_, string output) = BuildProject(
+                                    buildArgs,
+                                    initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                                    dotnetWasmFromRuntimePack: false,
+                                    id: id,
+                                    expectSuccess: false);
+
+            Assert.Contains("Stopping after AOT", output);
+        }
+
+        [Theory]
         [BuildAndRun(host: RunHost.None, aot: true)]
         public void IntermediateBitcodeToObjectFilesAreNotLLVMIR(BuildArgs buildArgs, string id)
         {
             string printFileTypeTarget = @"
-                <Target Name=""PrintIntermediateFileType"" AfterTargets=""WasmBuildApp"">
+                <Target Name=""PrintIntermediateFileType"" AfterTargets=""WasmNestedPublishApp"">
                     <Exec Command=""wasm-dis $(_WasmIntermediateOutputPath)System.Private.CoreLib.dll.o -o $(_WasmIntermediateOutputPath)wasm-dis-out.txt""
-                          ConsoleToMSBuild=""true""
                           EnvironmentVariables=""@(EmscriptenEnvVars)""
                           IgnoreExitCode=""true"">
 
                         <Output TaskParameter=""ExitCode"" PropertyName=""ExitCode"" />
                     </Exec>
 
-                    <Message Text=""wasm-dis exit code: $(ExitCode)"" Importance=""High"" />
+                    <Message Text=""
+                    ** wasm-dis exit code: $(ExitCode)
+                    "" Importance=""High"" />
                 </Target>
                 ";
             string projectName = $"bc_to_o_{buildArgs.Config}";
@@ -71,9 +93,41 @@ namespace Wasm.Build.Tests
                                     dotnetWasmFromRuntimePack: false,
                                     id: id);
 
-            if (!output.Contains("wasm-dis exit code: 0"))
+            if (!output.Contains("** wasm-dis exit code: 0"))
                 throw new XunitException($"Expected to successfully run wasm-dis on System.Private.CoreLib.dll.o ."
                                             + " It might fail if it was incorrectly compiled to a bitcode file, instead of wasm.");
         }
+
+        [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
+        [InlineData("Debug")]
+        [InlineData("Release")]
+        public void BlazorWasm_CanRunMonoAOTCross_WithNoTrimming(string config)
+        {
+            string id = $"blazorwasm_{config}_aot";
+            CreateBlazorWasmTemplateProject(id);
+
+            // We don't want to emcc compile, and link ~180 assemblies!
+            // So, stop once `mono-aot-cross` part of the build is done
+            string target = @"<Target Name=""StopAfterWasmAOT"" AfterTargets=""_WasmAotCompileApp"">
+                <Error Text=""Stopping after AOT"" Condition=""'$(WasmBuildingForNestedPublish)' == 'true'"" />
+            </Target>
+            ";
+            AddItemsPropertiesToProject(Path.Combine(_projectDir!, $"{id}.csproj"),
+                                        extraItems: null,
+                                        extraProperties: null,
+                                        atTheEnd: target);
+
+            string publishLogPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}.binlog");
+            CommandResult res = new DotNetCommand(s_buildEnv)
+                                        .WithWorkingDirectory(_projectDir!)
+                                        .ExecuteWithCapturedOutput("publish",
+                                                                   $"-bl:{publishLogPath}",
+                                                                   "-p:RunAOTCompilation=true",
+                                                                   "-p:PublishTrimmed=false",
+                                                                   $"-p:Configuration={config}");
+
+            Assert.True(res.ExitCode != 0, "Expected publish to fail");
+            Assert.Contains("Stopping after AOT", res.Output);
+        }
     }
 }
index eb30628..3ccf400 100644 (file)
@@ -1,7 +1,6 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System;
 using System.IO;
 using Xunit;
 using Xunit.Abstractions;
index 7ebc2d0..b73e521 100644 (file)
@@ -5,12 +5,13 @@ using System;
 using System.IO;
 using System.Collections.Generic;
 using System.Linq;
+using Wasm.Build.Tests;
 using Xunit;
 using Xunit.Abstractions;
 
 #nullable enable
 
-namespace Wasm.Build.Tests
+namespace Wasm.Build.NativeRebuild.Tests
 {
     public class FlagsChangeRebuildTest : NativeRebuildTestsBase
     {
index 2256fa5..9047fd0 100644 (file)
@@ -5,6 +5,7 @@ using System;
 using System.IO;
 using System.Collections.Generic;
 using System.Linq;
+using Wasm.Build.Tests;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Sdk;
@@ -12,7 +13,7 @@ using System.Text;
 
 #nullable enable
 
-namespace Wasm.Build.Tests
+namespace Wasm.Build.NativeRebuild.Tests
 {
     // TODO: test for runtime components
     public class NativeRebuildTestsBase : BuildTestBase
@@ -136,29 +137,6 @@ namespace Wasm.Build.Tests
                 throw new XunitException($"CompareStat failed:{Environment.NewLine}{msg}");
         }
 
-        internal IDictionary<string, FileStat> StatFiles(IEnumerable<string> fullpaths)
-        {
-            Dictionary<string, FileStat> table = new();
-            foreach (string file in fullpaths)
-            {
-                if (File.Exists(file))
-                    table.Add(Path.GetFileName(file), new FileStat(FullPath: file, Exists: true, LastWriteTimeUtc: File.GetLastWriteTimeUtc(file), Length: new FileInfo(file).Length));
-                else
-                    table.Add(Path.GetFileName(file), new FileStat(FullPath: file, Exists: false, LastWriteTimeUtc: DateTime.MinValue, Length: 0));
-            }
-
-            return table;
-        }
-
-        internal BuildPaths GetBuildPaths(BuildArgs buildArgs)
-        {
-            string objDir = GetObjDir(buildArgs.Config);
-            string bundleDir = Path.Combine(GetBinDir(baseDir: _projectDir, config: buildArgs.Config), "AppBundle");
-            string wasmDir = Path.Combine(objDir, "wasm");
-
-            return new BuildPaths(wasmDir, objDir, GetBinDir(buildArgs.Config), bundleDir);
-        }
-
         internal IDictionary<string, (string fullPath, bool unchanged)> GetFilesTable(BuildArgs buildArgs, BuildPaths paths, bool unchanged)
         {
             List<string> files = new()
@@ -203,7 +181,4 @@ namespace Wasm.Build.Tests
                 Assert.DoesNotContain(substring, full);
         }
     }
-
-    internal record FileStat (bool Exists, DateTime LastWriteTimeUtc, long Length, string FullPath);
-    internal record BuildPaths(string ObjWasmDir, string ObjDir, string BinDir, string BundleDir);
 }
index 1ddbd44..96f6d41 100644 (file)
@@ -2,12 +2,13 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Linq;
+using Wasm.Build.Tests;
 using Xunit;
 using Xunit.Abstractions;
 
 #nullable enable
 
-namespace Wasm.Build.Tests
+namespace Wasm.Build.NativeRebuild.Tests
 {
     public class NoopNativeRebuildTest : NativeRebuildTestsBase
     {
index d908e28..5aa3d29 100644 (file)
@@ -3,12 +3,13 @@
 
 using System.IO;
 using System.Linq;
+using Wasm.Build.Tests;
 using Xunit;
 using Xunit.Abstractions;
 
 #nullable enable
 
-namespace Wasm.Build.Tests
+namespace Wasm.Build.NativeRebuild.Tests
 {
     public class ReferenceNewAssemblyRebuildTest : NativeRebuildTestsBase
     {
index 7f51447..bbe7d60 100644 (file)
@@ -3,12 +3,13 @@
 
 using System.IO;
 using System.Linq;
+using Wasm.Build.Tests;
 using Xunit;
 using Xunit.Abstractions;
 
 #nullable enable
 
-namespace Wasm.Build.Tests
+namespace Wasm.Build.NativeRebuild.Tests
 {
     public class SimpleSourceChangeRebuildTest : NativeRebuildTestsBase
     {
index 45ce2b3..c3a40b4 100644 (file)
@@ -40,12 +40,13 @@ namespace Wasm.Build.Tests
                                               string id)
         {
             string projectName = $"sat_asm_from_main_asm";
-            bool dotnetWasmFromRuntimePack = !nativeRelink && !buildArgs.AOT;
+            // Release+publish defaults to native relinking
+            bool dotnetWasmFromRuntimePack = !nativeRelink && !buildArgs.AOT && buildArgs.Config != "Release";
 
             buildArgs = buildArgs with { ProjectName = projectName };
             buildArgs = ExpandBuildArgs(buildArgs,
                                         projectTemplate: s_resourcesProjectTemplate,
-                                        extraProperties: $"<WasmBuildNative>{(nativeRelink ? "true" : "false")}</WasmBuildNative>");
+                                        extraProperties: nativeRelink ? $"<WasmBuildNative>true</WasmBuildNative>" : string.Empty);
 
             BuildProject(buildArgs,
                         initProject: () =>
index 03437e6..019391f 100644 (file)
@@ -16,15 +16,20 @@ namespace Wasm.Build.Tests
         public Dictionary<BuildArgs, BuildProduct> _buildPaths = new();
 
         public void CacheBuild(BuildArgs buildArgs, BuildProduct product)
-            => _buildPaths.Add(buildArgs, product);
+        {
+            if (product == null)
+                throw new ArgumentNullException(nameof(product));
+            if (buildArgs == null)
+                throw new ArgumentNullException(nameof(buildArgs));
+            _buildPaths.Add(buildArgs, product);
+        }
 
         public void RemoveFromCache(string buildPath, bool keepDir=true)
         {
-            KeyValuePair<BuildArgs, BuildProduct>? foundKvp = _buildPaths.Where(kvp => kvp.Value.ProjectDir == buildPath).SingleOrDefault();
-            if (foundKvp == null)
-                throw new Exception($"Could not find build path {buildPath} in cache to remove.");
+            BuildArgs? foundBuildArgs = _buildPaths.Where(kvp => kvp.Value.ProjectDir == buildPath).Select(kvp => kvp.Key).SingleOrDefault();
+            if (foundBuildArgs is not null)
+                _buildPaths.Remove(foundBuildArgs);
 
-            _buildPaths.Remove(foundKvp.Value.Key);
             if (!keepDir)
                 RemoveDirectory(buildPath);
         }
index 7dacbc3..6c9a703 100644 (file)
@@ -7,7 +7,6 @@
     <BundleXunitRunner>true</BundleXunitRunner>
     <CLRTestKind>BuildAndRun</CLRTestKind>
     <TestFramework>xunit</TestFramework>
-    <WasmGenerateAppBundle>false</WasmGenerateAppBundle>
     <EnableDefaultItems>true</EnableDefaultItems>
     <EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
     <DefineConstants Condition="'$(ContinuousIntegrationBuild)' != 'true'">TEST_DEBUG_CONFIG_ALSO</DefineConstants>
@@ -18,7 +17,7 @@
     <InstallWorkloadForTesting>true</InstallWorkloadForTesting>
 
     <!-- don't run any wasm build steps -->
-    <WasmBuildAppAfterThisTarget />
+    <IsWasmProject>false</IsWasmProject>
   </PropertyGroup>
 
   <PropertyGroup>
diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmNativeDefaultsTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmNativeDefaultsTests.cs
new file mode 100644 (file)
index 0000000..64d0085
--- /dev/null
@@ -0,0 +1,101 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using Xunit;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+#nullable enable
+
+namespace Wasm.Build.Tests
+{
+    public class WasmNativeDefaultsTests : BuildTestBase
+    {
+        public WasmNativeDefaultsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
+            : base(output, buildContext)
+        {
+        }
+
+        [Theory]
+        /* relink by default for publish+Release */
+        [InlineData("Release",   "",                                         /*aot*/ false,   /*build*/ false, /*publish*/      true)]
+        /* NO relink by default for publish+Release, even when not trimming */
+        [InlineData("Release",   "<PublishTrimmed>false</PublishTrimmed>",   /*aot*/ false,   /*build*/ false, /*publish*/      false)]
+
+        [InlineData("Debug",     "",                                         /*aot*/ false,   /*build*/ false, /*publish*/      false)]
+
+        /* AOT */
+        [InlineData("Release",   "",                                         /*aot*/ true,    /*build*/ false, /*publish*/      true)]
+        [InlineData("Debug",     "",                                         /*aot*/ true,    /*build*/ false, /*publish*/      true)]
+        // FIXME: separate test
+        // [InlineData("Release",   "<RunAOTCompilationAfterBuild>true</RunAOTCompilationAfterBuild>",
+                                                                            //  /*aot*/ true,    /*build*/ true, /*publish*/      true)]
+
+        /* AOT not affected by trimming */
+        [InlineData("Release",   "<PublishTrimmed>false</PublishTrimmed>",   /*aot*/ true,    /*build*/ false, /*publish*/      true)]
+        [InlineData("Debug",     "<PublishTrimmed>false</PublishTrimmed>",   /*aot*/ true,    /*build*/ false, /*publish*/      true)]
+        public void Defaults(string config, string extraProperties, bool aot, bool buildValue, bool publishValue)
+        {
+            string output = CheckWasmNativeDefaultValue("native_defaults_publish", config, extraProperties, aot, dotnetWasmFromRuntimePack: !publishValue);
+
+            Assert.Contains($"** WasmBuildNative: '{buildValue.ToString().ToLower()}', WasmBuildingForNestedPublish: ''", output);
+            Assert.Contains($"** WasmBuildNative: '{publishValue.ToString().ToLower()}', WasmBuildingForNestedPublish: 'true'", output);
+            Assert.Contains("Stopping the build", output);
+        }
+
+        [Theory]
+        /* always relink */
+        [InlineData("Release", "",   /*build*/ true, /*publish*/ true)]
+        [InlineData("Debug",   "",   /*build*/ true, /*publish*/ true)]
+        [InlineData("Release", "<PublishTrimmed>false</PublishTrimmed>", /*build*/ true, /*publish*/ true)]
+        public void WithNativeReference(string config, string extraProperties, bool buildValue, bool publishValue)
+        {
+            string nativeLibPath = Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "native-lib.o");
+            string nativeRefItem = @$"<NativeFileReference Include=""{nativeLibPath}"" />";
+            string output = CheckWasmNativeDefaultValue("native_defaults_publish",
+                                                        config,
+                                                        extraProperties,
+                                                        aot: false,
+                                                        dotnetWasmFromRuntimePack: !publishValue,
+                                                        extraItems: nativeRefItem);
+
+            Assert.Contains($"** WasmBuildNative: '{buildValue.ToString().ToLower()}', WasmBuildingForNestedPublish: ''", output);
+            Assert.Contains($"** WasmBuildNative: '{publishValue.ToString().ToLower()}', WasmBuildingForNestedPublish: 'true'", output);
+            Assert.Contains("Stopping the build", output);
+        }
+
+        private string CheckWasmNativeDefaultValue(string projectName,
+                                                   string config,
+                                                   string extraProperties,
+                                                   bool aot,
+                                                   bool dotnetWasmFromRuntimePack,
+                                                   string extraItems = "")
+        {
+            // builds with -O0
+            extraProperties += "<_WasmDevel>true</_WasmDevel>";
+
+            string printValueTarget = @"
+                <Target Name=""PrintWasmBuildNative"" AfterTargets=""_SetWasmBuildNativeDefaults"">
+                    <Message Text=""** WasmBuildNative: '$(WasmBuildNative)', WasmBuildingForNestedPublish: '$(WasmBuildingForNestedPublish)'"" Importance=""High"" />
+                    <Error Text=""Stopping the build"" Condition=""$(WasmBuildingForNestedPublish) == 'true'"" />
+                </Target>";
+
+            BuildArgs buildArgs = new(ProjectName: projectName, Config: config, AOT: aot, string.Empty, null);
+            buildArgs = ExpandBuildArgs(buildArgs,
+                                        extraProperties: extraProperties,
+                                        extraItems: extraItems,
+                                        insertAtEnd: printValueTarget);
+
+            (_, string output) = BuildProject(buildArgs,
+                                    initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                                    dotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack,
+                                    id: Path.GetRandomFileName(),
+                                    expectSuccess: false,
+                                    useCache: false);
+
+            return output;
+        }
+    }
+}
index f77fc96..5cef982 100644 (file)
@@ -10,7 +10,8 @@
 
   <!-- SDK tries to download runtime packs when RuntimeIdentifier is set, remove them from PackageDownload item. -->
   <Target Name="RemoveRuntimePackFromDownloadItem"
-          AfterTargets="ProcessFrameworkReferences">
+          AfterTargets="ProcessFrameworkReferences"
+          Condition="'$(WasmNativeWorkload)' == 'true'">
     <ItemGroup>
       <PackageDownload Remove="@(PackageDownload)"
                        Condition="'$(UsePackageDownload)' == 'true' and $([System.String]::Copy('%(Identity)').StartsWith('Microsoft.NETCore.App.Runtime'))" />
@@ -21,7 +22,8 @@
 
   <!-- Use local targeting pack for NetCoreAppCurrent. -->
   <Target Name="UpdateTargetingAndRuntimePack"
-          AfterTargets="ResolveFrameworkReferences">
+          AfterTargets="ResolveFrameworkReferences"
+          Condition="'$(WasmNativeWorkload)' == 'true'">
     <ItemGroup>
       <ResolvedTargetingPack Path="$(_MicrosoftNetCoreAppRefDir.TrimEnd('/\'))"
                              NuGetPackageVersion="$(RuntimePackInWorkloadVersion)"
@@ -47,7 +49,8 @@
 
   <!-- Update the local targeting pack's version as it's written into the runtimeconfig.json file to select the right framework. -->
   <Target Name="UpdateRuntimeFrameworkVersion"
-          AfterTargets="ResolveTargetingPackAssets">
+          AfterTargets="ResolveTargetingPackAssets"
+          Condition="'$(WasmNativeWorkload)' == 'true'">
     <ItemGroup>
       <RuntimeFramework Version="$(RuntimePackInWorkloadVersion)"
                         Condition="'%(RuntimeFramework.FrameworkName)' == 'Microsoft.NETCore.App'" />
@@ -56,7 +59,7 @@
 
   <!-- Filter out conflicting implicit assembly references. -->
   <Target Name="FilterImplicitAssemblyReferences"
-          Condition="'$(DisableImplicitAssemblyReferences)' != 'true'"
+          Condition="'$(DisableImplicitAssemblyReferences)' != 'true' and '$(WasmNativeWorkload)' == 'true'"
           DependsOnTargets="ResolveProjectReferences"
           AfterTargets="ResolveTargetingPackAssets">
     <ItemGroup>
diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.props b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.props
new file mode 100644 (file)
index 0000000..909ea03
--- /dev/null
@@ -0,0 +1,11 @@
+<Project>
+  <PropertyGroup Condition="'$(RuntimeSrcDir)' != '' and '$(WasmBuildSupportDir)' == ''">
+    <ArtifactsBinDir>$(RuntimeSrcDir)\artifacts\bin\</ArtifactsBinDir>
+    <MicrosoftNetCoreAppRuntimePackLocationToUse>$([MSBuild]::NormalizeDirectory($(ArtifactsBinDir), 'microsoft.netcore.app.runtime.browser-wasm', $(RuntimeConfig)))</MicrosoftNetCoreAppRuntimePackLocationToUse>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(RuntimeSrcDir)' == '' and '$(WasmBuildSupportDir)' != ''">
+    <BuildBaseDir>$(WasmBuildSupportDir)\</BuildBaseDir>
+    <MicrosoftNetCoreAppRuntimePackLocationToUse>$([MSBuild]::NormalizeDirectory($(BuildBaseDir), 'microsoft.netcore.app.runtime.browser-wasm'))</MicrosoftNetCoreAppRuntimePackLocationToUse>
+  </PropertyGroup>
+</Project>
diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.targets b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.targets
new file mode 100644 (file)
index 0000000..639a841
--- /dev/null
@@ -0,0 +1,36 @@
+<Project>
+  <PropertyGroup>
+    <_MicrosoftNetCoreAppRefDir>$(AppRefDir)\</_MicrosoftNetCoreAppRefDir>
+  </PropertyGroup>
+
+  <Target Name="PrintRuntimePackPath" BeforeTargets="Publish">
+    <Message Text="** MicrosoftNetCoreAppRuntimePackDir : %(ResolvedRuntimePack.PackageDirectory)" Importance="High" />
+  </Target>
+
+  <!-- Use local targeting pack for NetCoreAppCurrent. -->
+  <Target Name="UpdateTargetingAndRuntimePack"
+          AfterTargets="ResolveFrameworkReferences">
+    <ItemGroup>
+      <ResolvedTargetingPack Path="$(_MicrosoftNetCoreAppRefDir.TrimEnd('/\'))"
+                             NuGetPackageVersion="$(RuntimePackInWorkloadVersion)"
+                             PackageDirectory="$(_MicrosoftNetCoreAppRefDir.TrimEnd('/\'))"
+                             Condition="'%(ResolvedTargetingPack.RuntimeFrameworkName)' == 'Microsoft.NETCore.App' and
+                                        Exists('$(_MicrosoftNetCoreAppRefDir)data\FrameworkList.xml')" />
+
+      <ResolvedRuntimePack
+                            Update="Microsoft.NETCore.App.Runtime.Mono.browser-wasm"
+                            FrameworkName="Microsoft.NETCore.App"
+                            NuGetPackageId="Microsoft.NETCore.App.Runtime.Mono.browser-wasm"
+                            NuGetPackageVersion="$(RuntimePackInWorkloadVersion)"
+                            PackageDirectory="$(MicrosoftNetCoreAppRuntimePackLocationToUse)"
+                            RuntimeIdentifier="browser-wasm" />
+
+       <ResolvedFrameworkReference Update="Microsoft.NETCore.App"
+                                   TargetingPackPath="$(_MicrosoftNetCoreAppRefDir.TrimEnd('/\'))"
+                                   RuntimePackName="Microsoft.NETCore.App.Runtime.Mono.browser-wasm"
+                                   RuntimePackVersion="$(RuntimePackInWorkloadVersion)"
+                                   RuntimePackPath="$(MicrosoftNetCoreAppRuntimePackLocationToUse)"
+                                   RuntimeIdentifier="browser-wasm" />
+    </ItemGroup>
+  </Target>
+</Project>
index 1a9c112..6d53e53 100644 (file)
@@ -7,8 +7,4 @@
   </PropertyGroup>
 
   <Import Project="$(_WasmTargetsDir)WasmApp.LocalBuild.props" Condition="Exists('$(_WasmTargetsDir)WasmApp.LocalBuild.props')" />
-
-  <PropertyGroup>
-    <WasmBuildAppDependsOn>PrepareForWasmBuild;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
-  </PropertyGroup>
 </Project>
index 65f76e4..b327d1e 100644 (file)
       Text="%24(WasmMainJS) is set when %24(WasmGenerateAppBundle) is not true: it won't be used because an app bundle is not being generated. Possible build authoring error" />
   </Target>
 
-  <Target Name="PrepareForWasmBuild">
-    <ItemGroup>
-      <WasmAssembliesToBundle Include="$(TargetDir)publish\**\*.dll" />
-    </ItemGroup>
-  </Target>
-
   <Target Name="PrintRuntimePackPath" BeforeTargets="Build">
     <Message Text="** MicrosoftNetCoreAppRuntimePackDir : %(ResolvedRuntimePack.PackageDirectory)" Importance="High" />
   </Target>
index 105c0a7..34eeeae 100644 (file)
@@ -1,15 +1,8 @@
 <Project>
   <PropertyGroup>
-    <WasmBuildAppDependsOn>PrepareForWasmBuild;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
     <_MicrosoftNetCoreAppRefDir>$(AppRefDir)\</_MicrosoftNetCoreAppRefDir>
   </PropertyGroup>
 
-  <Target Name="PrepareForWasmBuild">
-    <ItemGroup>
-      <WasmAssembliesToBundle Include="$(TargetDir)publish\**\*.dll" />
-    </ItemGroup>
-  </Target>
-
   <Target Name="PrintRuntimePackPath" BeforeTargets="Publish">
     <Message Text="** MicrosoftNetCoreAppRuntimePackDir : %(ResolvedRuntimePack.PackageDirectory)" Importance="High" />
   </Target>
index 5f4d4f0..e2eda44 100644 (file)
     <BuildDir>$(MSBuildThisFileDirectory)\obj\$(Configuration)\wasm</BuildDir>
     <AppDir>$(TestBinDir)/WasmApp/</AppDir>
     <NETCoreAppMaximumVersion>99.0</NETCoreAppMaximumVersion>
+    <IsWasmProject>true</IsWasmProject>
+    <WasmGenerateAppBundle>true</WasmGenerateAppBundle>
 
     <WasmAppBuilderTasksAssemblyPath>$(CORE_ROOT)\WasmAppBuilder\WasmAppBuilder.dll</WasmAppBuilderTasksAssemblyPath>
     <MonoAOTCompilerTasksAssemblyPath>$(CORE_ROOT)\MonoAOTCompiler\MonoAOTCompiler.dll</MonoAOTCompilerTasksAssemblyPath>
     <JsonToItemsTaskFactoryTasksAssemblyPath>$(CORE_ROOT)\JsonToItemsTaskFactory\JsonToItemsTaskFactory.dll</JsonToItemsTaskFactoryTasksAssemblyPath>
     <RuntimeConfigParserTasksAssemblyPath>$(CORE_ROOT)\RuntimeConfigParser\RuntimeConfigParser.dll</RuntimeConfigParserTasksAssemblyPath>
+    <WasmBuildAppDependsOn>BuildApp;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
   </PropertyGroup>
 
-  <Target Name="BuildApp" BeforeTargets="WasmBuildApp">
+  <Target Name="BuildApp">
     <PropertyGroup>
       <WasmMainAssemblyFileName>$(TestAssemblyFileName)</WasmMainAssemblyFileName>
       <WasmAppDir>$(AppDir)</WasmAppDir>
index 2ba0552..d9dbaa2 100644 (file)
     <Content Include="index.html">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
+    <WasmExtraFilesToDeploy Include="index.html" />
     <ProjectReference Include="ApplyUpdateReferencedAssembly\ApplyUpdateReferencedAssembly.csproj" />
   </ItemGroup>
 
-  <Target Name="AfterWasmBuildApp" AfterTargets="WasmBuildApp">
-    <Copy SourceFiles="$(OutDir)\index.html" DestinationFolder="$(WasmAppDir)" />
-  </Target>
-
   <Target Name="PreserveEnCAssembliesFromLinking"
           Condition="'$(TargetOS)' == 'Browser' and '$(EnableAggressiveTrimming)' == 'true'"
           BeforeTargets="ConfigureTrimming">
index 4efa17a..35e9eec 100644 (file)
@@ -5,13 +5,9 @@
     <ExpectedExitCode>42</ExpectedExitCode>
     <WasmMainJSPath>runtime.js</WasmMainJSPath>
   </PropertyGroup>
-  
+
   <ItemGroup>
     <Compile Include="Program.cs" />
+    <WasmExtraFilesToDeploy Include="index.html" />
   </ItemGroup>
-
-  <Target Name="AfterWasmBuildApp" AfterTargets="WasmBuildApp">
-    <Copy SourceFiles="$(MSBuildThisFileDirectory)\index.html" DestinationFolder="$(WasmAppDir)" />
-  </Target>
-  
 </Project>
index b0d320f..802f252 100644 (file)
@@ -2,13 +2,6 @@
   <Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.targets, '$(MSBuildThisFileDirectory)..'))" />
 
   <PropertyGroup>
-    <WasmBuildAppDependsOn>PrepareForWasmBuild;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
     <WasmAppDir>$(OutputPath)\$(Configuration)\AppBundle\</WasmAppDir>
   </PropertyGroup>
-
-  <Target Name="PrepareForWasmBuild">
-    <ItemGroup>
-      <WasmAssembliesToBundle Include="$(TargetDir)publish\*.dll" />
-    </ItemGroup>
-  </Target>
 </Project>