From 4af01264ae1537e9a0551406c6e58b181093a670 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Thu, 16 Sep 2021 23:38:26 +0000 Subject: [PATCH] =?UTF-8?q?[wasm]=20Add=20support=20for=20native=20relinki?= =?UTF-8?q?ng=20after=20Build,=20and=E2=80=A6=20(#59153)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … AOT after publish (#58913) Forward port #58913 (cherry picked from commit f38d58f) --- eng/Versions.props | 2 +- .../scenarios/BuildWasmAppsJobsList.txt | 32 +- eng/testing/tests.mobile.targets | 5 +- eng/testing/tests.wasm.targets | 22 +- src/libraries/sendtohelixhelp.proj | 6 +- src/libraries/workloads-testing.targets | 7 +- .../sample/Android/AndroidSampleApp.csproj | 1 + src/mono/sample/iOS/Program.csproj | 1 + src/mono/sample/mbr/browser/WasmDelta.csproj | 1 - src/mono/sample/wasm/Directory.Build.targets | 5 - src/mono/sample/wasm/console/Program.cs | 2 +- src/mono/sample/wasm/wasm.mk | 5 +- src/mono/wasm/BlazorOverwrite.targets | 741 ++++++++++++++++++ src/mono/wasm/build/README.md | 57 +- src/mono/wasm/build/WasmApp.InTree.targets | 6 +- .../wasm/build/WasmApp.LocalBuild.targets | 4 +- src/mono/wasm/build/WasmApp.Native.targets | 112 +-- src/mono/wasm/build/WasmApp.props | 13 +- src/mono/wasm/build/WasmApp.targets | 123 ++- .../aot-tests/ProxyProjectForAOTOnHelix.proj | 1 + .../wasm/debugger/tests/Directory.Build.props | 1 + .../tests/debugger-test/debugger-test.csproj | 1 + src/tasks/AotCompilerTask/MonoAOTCompiler.cs | 190 +++-- src/tasks/Common/Utils.cs | 2 +- src/tasks/WasmAppBuilder/EmccCompile.cs | 19 +- .../WasmAppBuilder/IcallTableGenerator.cs | 4 + .../WasmAppBuilder/PInvokeTableGenerator.cs | 42 +- src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 25 +- .../WasmAppBuilder/WasmAppBuilder.csproj | 1 + .../BlazorWasmBuildPublishTests.cs | 194 +++++ .../Wasm.Build.Tests/BlazorWasmTests.cs | 62 +- .../Wasm.Build.Tests/BuildAndRunAttribute.cs | 4 +- .../Wasm.Build.Tests/BuildEnvironment.cs | 3 + .../Wasm.Build.Tests/BuildPublishTests.cs | 158 ++++ .../Wasm.Build.Tests/BuildTestBase.cs | 207 ++++- .../Wasm.Build.Tests/CleanTests.cs | 110 +++ .../Wasm.Build.Tests/CommandResult.cs | 6 +- .../Wasm.Build.Tests/HelperExtensions.cs | 5 +- .../Wasm.Build.Tests/NativeBuildTests.cs | 72 +- .../Wasm.Build.Tests/NativeLibraryTests.cs | 1 - .../FlagsChangeRebuildTest.cs | 3 +- .../NativeRebuildTestsBase.cs | 29 +- .../NoopNativeRebuildTest.cs | 3 +- .../ReferenceNewAssemblyRebuildTest.cs | 3 +- .../SimpleSourceChangeRebuildTest.cs | 3 +- .../SatelliteAssembliesTests.cs | 5 +- .../SharedBuildPerTestClassFixture.cs | 15 +- .../Wasm.Build.Tests/Wasm.Build.Tests.csproj | 3 +- .../WasmNativeDefaultsTests.cs | 101 +++ .../data/Blazor.Directory.Build.targets | 11 +- .../data/Blazor.Local.Directory.Build.props | 11 + .../data/Blazor.Local.Directory.Build.targets | 36 + .../data/Local.Directory.Build.props | 4 - .../data/Local.Directory.Build.targets | 6 - .../data/Workloads.Directory.Build.targets | 7 - .../wasm-test-runner/WasmTestRunner.proj | 5 +- .../WebAssembly.Browser.HotReload.Test.csproj | 5 +- ...Assembly.Browser.RuntimeConfig.Test.csproj | 8 +- .../WebAssembly/Directory.Build.targets | 7 - 59 files changed, 2183 insertions(+), 335 deletions(-) create mode 100644 src/mono/wasm/BlazorOverwrite.targets create mode 100644 src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs create mode 100644 src/tests/BuildWasmApps/Wasm.Build.Tests/BuildPublishTests.cs create mode 100644 src/tests/BuildWasmApps/Wasm.Build.Tests/CleanTests.cs create mode 100644 src/tests/BuildWasmApps/Wasm.Build.Tests/WasmNativeDefaultsTests.cs create mode 100644 src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.props create mode 100644 src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.targets diff --git a/eng/Versions.props b/eng/Versions.props index 6c185b41e49..b144cc27adf 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -164,7 +164,7 @@ 2.0.4 4.12.0 2.14.3 - 6.0.100-rc.2.21425.12 + 6.0.100-rc.2.21463.12 6.0.0-preview-20210916.1 diff --git a/eng/testing/scenarios/BuildWasmAppsJobsList.txt b/eng/testing/scenarios/BuildWasmAppsJobsList.txt index ba322274dfe..c4c5a1875c3 100644 --- a/eng/testing/scenarios/BuildWasmAppsJobsList.txt +++ b/eng/testing/scenarios/BuildWasmAppsJobsList.txt @@ -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 diff --git a/eng/testing/tests.mobile.targets b/eng/testing/tests.mobile.targets index c62916ed2d1..8a7036f1420 100644 --- a/eng/testing/tests.mobile.targets +++ b/eng/testing/tests.mobile.targets @@ -10,6 +10,7 @@ true BundleTestAppleApp;BundleTestAndroidApp + Publish @@ -111,6 +112,7 @@ OutputType="AsmOnly" Assemblies="@(AotInputAssemblies)" AotModulesTablePath="$(BundleDir)\modules.c" + IntermediateOutputPath="$(IntermediateOutputPath)" UseLLVM="$(MonoEnableLLVM)" LLVMPath="$(MonoAotCrossDir)"> @@ -200,6 +202,7 @@ Assemblies="@(AotInputAssemblies)" AotModulesTablePath="$(BundleDir)\modules.m" AotModulesTableLanguage="ObjC" + IntermediateOutputPath="$(IntermediateOutputPath)" UseLLVM="$(MonoEnableLLVM)" LLVMPath="$(MonoAotCrossDir)"> @@ -306,7 +309,7 @@ + DependsOnTargets="$(PublishTestAsSelfContainedDependsOn);$(BundleTestAppTargets);ArchiveTests" /> + true + true $(BundleTestAppTargets);BundleTestWasmApp true + + + + true - WasmBuildApp - $(BundleTestWasmAppDependsOn);_BundleAOTTestWasmAppForHelix + + + PrepareForWasmBuildApp;$(WasmNestedPublishAppDependsOn) @@ -80,6 +92,8 @@ $([System.IO.Path]::ChangeExtension($(_MainAssemblyPath), '.runtimeconfig.json')) + + @@ -154,7 +168,7 @@ - + <_CopyLocalPaths diff --git a/src/libraries/sendtohelixhelp.proj b/src/libraries/sendtohelixhelp.proj index c72a5c1da28..8d42aba4a4e 100644 --- a/src/libraries/sendtohelixhelp.proj +++ b/src/libraries/sendtohelixhelp.proj @@ -426,10 +426,10 @@ - + $(_BuildWasmAppsPayloadArchive) - set "HELIX_XUNIT_ARGS=-class Wasm.Build.Tests.%(Identity)" - export "HELIX_XUNIT_ARGS=-class Wasm.Build.Tests.%(Identity)" + set "HELIX_XUNIT_ARGS=-class %(Identity)" + export "HELIX_XUNIT_ARGS=-class %(Identity)" $(HelixCommand) $(_workItemTimeout) diff --git a/src/libraries/workloads-testing.targets b/src/libraries/workloads-testing.targets index 3e668a38c24..d3dc57267a6 100644 --- a/src/libraries/workloads-testing.targets +++ b/src/libraries/workloads-testing.targets @@ -6,7 +6,7 @@ - + @@ -16,6 +16,8 @@ + + @@ -23,7 +25,7 @@ - + @@ -39,6 +41,7 @@ + diff --git a/src/mono/sample/Android/AndroidSampleApp.csproj b/src/mono/sample/Android/AndroidSampleApp.csproj index 1abf7659bd0..a5129bdfe82 100644 --- a/src/mono/sample/Android/AndroidSampleApp.csproj +++ b/src/mono/sample/Android/AndroidSampleApp.csproj @@ -72,6 +72,7 @@ AotModulesTablePath="$(_AotModulesTablePath)" ToolPrefix="$(_AotToolPrefix)" LibraryFormat="$(_AotLibraryFormat)" + IntermediateOutputPath="$(IntermediateOutputPath)" UseLLVM="$(UseLLVM)" LLVMPath="$(MonoAotCrossDir)"> diff --git a/src/mono/sample/iOS/Program.csproj b/src/mono/sample/iOS/Program.csproj index 50098f2d2e8..fc2f27d7a61 100644 --- a/src/mono/sample/iOS/Program.csproj +++ b/src/mono/sample/iOS/Program.csproj @@ -60,6 +60,7 @@ AotModulesTablePath="$(AppDir)\modules.m" AotModulesTableLanguage="ObjC" OutputDir="$(PublishDir)" + IntermediateOutputPath="$(IntermediateOutputPath)" UseLLVM="$(UseLLVM)" LLVMPath="$(MonoAotCrossDir)"> diff --git a/src/mono/sample/mbr/browser/WasmDelta.csproj b/src/mono/sample/mbr/browser/WasmDelta.csproj index 322a01ba859..c150529f753 100644 --- a/src/mono/sample/mbr/browser/WasmDelta.csproj +++ b/src/mono/sample/mbr/browser/WasmDelta.csproj @@ -31,7 +31,6 @@ - \%(_DeltaFileForPublish.Filename)%(_DeltaFileForPublish.Extension) diff --git a/src/mono/sample/wasm/Directory.Build.targets b/src/mono/sample/wasm/Directory.Build.targets index 492c61cf048..bc7f6b24b0b 100644 --- a/src/mono/sample/wasm/Directory.Build.targets +++ b/src/mono/sample/wasm/Directory.Build.targets @@ -1,10 +1,5 @@ - - - - - Main(string[] args) } return args.Length; } -} \ No newline at end of file +} diff --git a/src/mono/sample/wasm/wasm.mk b/src/mono/sample/wasm/wasm.mk index 097eeb8ed71..c9bbdd00a41 100644 --- a/src/mono/sample/wasm/wasm.mk +++ b/src/mono/sample/wasm/wasm.mk @@ -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 index 00000000000..a276d385723 --- /dev/null +++ b/src/mono/wasm/BlazorOverwrite.targets @@ -0,0 +1,741 @@ + + + + + true + + + true + + + + + $(MSBuildThisFileDirectory)..\ + <_BlazorWebAssemblySdkTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">net6.0 + <_BlazorWebAssemblySdkTasksTFM Condition=" '$(MSBuildRuntimeType)' != 'Core'">net472 + <_BlazorWebAssemblySdkTasksAssembly>$(BlazorWebAssemblySdkDirectoryRoot)tools\$(_BlazorWebAssemblySdkTasksTFM)\Microsoft.NET.Sdk.BlazorWebAssembly.Tasks.dll + <_BlazorWebAssemblySdkToolAssembly>$(BlazorWebAssemblySdkDirectoryRoot)tools\net6.0\Microsoft.NET.Sdk.BlazorWebAssembly.Tool.dll + + + + + + + + + + + + + + true + true + + + false + false + true + false + false + false + <_AggressiveAttributeTrimming Condition="'$(_AggressiveAttributeTrimming)' == ''">true + false + true + + + false + false + false + false + true + + + false + + <_TargetingNET60OrLater Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '6.0'))">true + + + + false + + true + true + false + ComputeFilesToPublish;_GatherWasmFilesToPublish;$(WasmNestedPublishAppDependsOn) + <_ScrambleDotnetJsFileNameAfterThisTarget Condition="'$(UsingBrowserRuntimeWorkload)' != 'true'">ResolveRuntimePackAssets + <_ScrambleDotnetJsFileNameAfterThisTarget Condition="'$(UsingBrowserRuntimeWorkload)' == 'true'">WasmBuildApp + + + Publish + + + + + + + + + + + + $(ResolveStaticWebAssetsInputsDependsOn); + _AddBlazorWasmStaticWebAssets; + + + + _GenerateBuildBlazorBootJson; + $(StaticWebAssetsPrepareForRunDependsOn) + + + + $(ResolvePublishStaticWebAssetsDependsOn); + ProcessPublishFilesForBlazor; + ComputeBlazorExtensions; + _AddPublishBlazorBootJsonToStaticWebAssets; + + + + $(GenerateStaticWebAssetsPublishManifestDependsOn); + GeneratePublishBlazorBootJson; + + + + + + + + + + <_DotNetJsVersion>$(BundledNETCoreAppPackageVersion) + <_DotNetJsVersion Condition="'$(RuntimeFrameworkVersion)' != ''">$(RuntimeFrameworkVersion) + <_BlazorDotnetJsFileName>dotnet.$(_DotNetJsVersion).js + + + + <_DotNetJsItem Remove="@(_DotNetJsItem)" /> + <_DotNetJsItem Include="@(WasmNativeAsset)" Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'"> + _framework/$(_BlazorDotnetJsFileName) + native + true + + + + + <_DotnetJsStaticWebAssetCandidate Remove="@(_DotnetJsStaticWebAssetCandidate)" /> + <_DotnetJsCopyCandidates Remove="@(_DotnetJsCopyCandidates)" /> + + + + + + + + + + + + <_DotnetJsStaticWebAsset Include="@(_DotnetJsStaticWebAssetCandidate->'%(ContentRoot)_framework\dotnet.js')" /> + <_BlazorStaticWebAsset Include="@(_DotnetJsStaticWebAsset)" /> + + + + + + + <_DotNetJsVersion>$(BundledNETCoreAppPackageVersion) + <_DotNetJsVersion Condition="'$(RuntimeFrameworkVersion)' != ''">$(RuntimeFrameworkVersion) + <_BlazorDotnetJsFileName>dotnet.$(_DotNetJsVersion).js + + + + <_DotNetJsItem Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.DestinationSubPath)' == 'dotnet.js' AND '%(ReferenceCopyLocalPaths.AssetType)' == 'native'"> + _framework/$(_BlazorDotnetJsFileName) + + + + + <_DotNetJsItem Remove="@(_DotNetJsItem)" /> + <_DotNetJsItem Include="@(WasmNativeAsset)" Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'"> + _framework/$(_BlazorDotnetJsFileName) + native + true + + + + + + + + + + + + + <_DotnetJsStaticWebAsset Include="@(_DotnetJsStaticWebAssetCandidate->'%(ContentRoot)_framework\dotnet.js')" /> + <_BlazorStaticWebAsset Include="@(_DotnetJsStaticWebAsset)" /> + + + + + + + + + + + + + + + + <_BlazorEnableTimeZoneSupport>$(BlazorEnableTimeZoneSupport) + <_BlazorEnableTimeZoneSupport Condition="'$(_BlazorEnableTimeZoneSupport)' == ''">true + <_BlazorInvariantGlobalization>$(InvariantGlobalization) + <_BlazorInvariantGlobalization Condition="'$(_BlazorInvariantGlobalization)' == ''">true + <_BlazorCopyOutputSymbolsToOutputDirectory>$(CopyOutputSymbolsToOutputDirectory) + <_BlazorCopyOutputSymbolsToOutputDirectory Condition="'$(_BlazorCopyOutputSymbolsToOutputDirectory)'==''">true + <_BlazorWebAssemblyLoadAllGlobalizationData>$(BlazorWebAssemblyLoadAllGlobalizationData) + <_BlazorWebAssemblyLoadAllGlobalizationData Condition="'$(_BlazorWebAssemblyLoadAllGlobalizationData)' == ''">false + + + $(OutputPath)$(PublishDirName)\ + + + + + + <_BlazorJSFile Include="$(BlazorWebAssemblyJSPath)" /> + <_BlazorJSFile Include="$(BlazorWebAssemblyJSMapPath)" Condition="Exists('$(BlazorWebAssemblyJSMapPath)')" /> + <_BlazorJsFile> + _framework/%(Filename)%(Extension) + + + + + + <_BlazorConfigFileCandidates Include="@(StaticWebAsset)" Condition="'%(SourceType)' == 'Discovered'" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_BlazorBuildGZipCompressDirectory>$(IntermediateOutputPath)build-gz\ + + + + + + + + + + + + + + + + <_BlazorBuildGZipCompressedFile> + %(RelatedAsset) + + + <_BlazorGzipStaticWebAsset Include="@(_BlazorBuildGZipCompressedFile->'%(FullPath)')" /> + + + + <_BlazorBuildBootJsonPath>$(IntermediateOutputPath)blazor.boot.json + + + + <_BuildBlazorBootJson + Include="$(_BlazorBuildBootJsonPath)" + RelativePath="_framework/blazor.boot.json" /> + + + + + + + + + + + + + + + + + + + + <_BlazorBuildBootJsonPath>$(IntermediateOutputPath)blazor.boot.json + <_BlazorWebAssemblyLoadAllGlobalizationData Condition="'$(BlazorWebAssemblyLoadAllGlobalizationData)' == ''">false + + + + <_BlazorJsModuleCandidatesForBuild + Include="@(StaticWebAsset)" + Condition="'%(StaticWebAsset.AssetTraitName)' == 'JSModule' and '%(StaticWebAsset.AssetTraitValue)' == 'JSLibraryModule' and '%(AssetKind)' != 'Publish'" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_BlazorTypeGranularTrimmerDescriptorFile>$(IntermediateOutputPath)typegranularity.trimmerdescriptor.xml + + + + <_BlazorTypeGranularAssembly + Include="@(ManagedAssemblyToLink)" + Condition="'%(Extension)' == '.dll' AND $([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))"> + false + all + + + + + + + + + + + + + + + + + <_BlazorAotEnabled>$(UsingBrowserRuntimeWorkload) + <_BlazorAotEnabled Condition="'$(_BlazorAotEnabled)' == ''">false + <_BlazorLinkerEnabled>$(PublishTrimmed) + <_BlazorLinkerEnabled Condition="'$(_BlazorLinkerEnabled)' == ''">true + + + + + + <_BlazorPublishPrefilteredAssets + Include="@(StaticWebAsset)" + Condition="'%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' or '%(StaticWebAsset.AssetTraitName)' == 'Culture' or '%(AssetRole)' == 'Alternative'" /> + + + + + + + + + + + + + + + + + <_BlazorExtensionsCandidate Include="@(BlazorPublishExtension->'%(FullPath)')"> + $(PackageId) + Computed + $(PublishDir)wwwroot + $(StaticWebAssetBasePath) + %(BlazorPublishExtension.RelativePath) + Publish + All + Primary + BlazorWebAssemblyResource + extension:%(BlazorPublishExtension.ExtensionName) + Never + PreserveNewest + %(BlazorPublishExtension.Identity) + + + + + + + + + + + + + + + + + + + <_PublishBlazorBootJson + Include="$(IntermediateOutputPath)blazor.publish.boot.json" + RelativePath="_framework/blazor.boot.json" /> + + + + + + + + + + + <_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'" /> + + + <_BlazorPublishAsset Remove="@(_BlazorExtensionsCandidatesForPublish)" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_CompressedFileOutputPath>$(IntermediateOutputPath)compress\ + <_BlazorWebAssemblyBrotliIncremental>true + + + + <_DotNetHostDirectory>$(NetCoreRoot) + <_DotNetHostFileName>dotnet + <_DotNetHostFileName Condition="'$(OS)' == 'Windows_NT'">dotnet.exe + + + + + + + + <_GzipFileToCompressForPublish Include="@(StaticWebAsset)" + Condition="'%(AssetKind)' != 'Build' and ('%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' or '%(StaticWebAsset.AssetTraitName)' == 'Culture')" > + %(Identity) + Alternative + Content-Encoding + gzip + + + <_BrotliFileToCompressForPublish Include="@(_GzipFileToCompressForPublish)" Condition="'%(AssetKind)' != 'Build'"> + br + + + + <_AlreadyGzipCompressedAssets + Include="@(StaticWebAsset)" + Condition="'%(AssetKind)' != 'Build' and ('%(StaticWebAsset.AssetTraitName)' == 'Content-Encoding' and '%(StaticWebAsset.AssetTraitValue)' == 'gzip')" /> + <_GzipFileToCompressForPublish Remove="@(_AlreadyGzipCompressedAssets->'%(RelatedAsset)')" /> + + + + + + + + + + + + + + + + <_BlazorPublishGZipCompressedFile> + %(RelatedAsset) + + <_BlazorPublishBrotliCompressedFile> + %(RelatedAsset) + + + + + + + + diff --git a/src/mono/wasm/build/README.md b/src/mono/wasm/build/README.md index 854ea041398..312b5308645 100644 --- a/src/mono/wasm/build/README.md +++ b/src/mono/wasm/build/README.md @@ -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` diff --git a/src/mono/wasm/build/WasmApp.InTree.targets b/src/mono/wasm/build/WasmApp.InTree.targets index b345da95cf9..f8c26080f02 100644 --- a/src/mono/wasm/build/WasmApp.InTree.targets +++ b/src/mono/wasm/build/WasmApp.InTree.targets @@ -17,7 +17,7 @@ Condition="'$(_LocalMicrosoftNetCoreAppRuntimePackDir)' != '' and '%(ResolvedRuntimePack.FrameworkName)' == 'Microsoft.NETCore.App'" /> - + @@ -35,9 +35,9 @@ + DependsOnTargets="WasmTriggerPublishApp"> $(MSBuildProjectName) diff --git a/src/mono/wasm/build/WasmApp.LocalBuild.targets b/src/mono/wasm/build/WasmApp.LocalBuild.targets index 23f5249786d..47559bccb84 100644 --- a/src/mono/wasm/build/WasmApp.LocalBuild.targets +++ b/src/mono/wasm/build/WasmApp.LocalBuild.targets @@ -25,7 +25,7 @@ - true + true link @@ -45,7 +45,7 @@ Condition="'$(MicrosoftNetCoreAppRuntimePackLocationToUse)' != '' and '%(ResolvedRuntimePack.FrameworkName)' == 'Microsoft.NETCore.App'" /> - + diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 03af11c734a..19737849bda 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -17,12 +17,6 @@ _CompleteWasmBuildNative - - _InitializeCommonProperties; - _PrepareForWasmBuildNativeOnly; - _WasmBuildNativeCore; - - <_BeforeWasmBuildAppDependsOn> $(_BeforeWasmBuildAppDependsOn); _SetupEmscripten; @@ -39,16 +33,6 @@ - - - - - - <_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)" /> - <_WasmAssembliesInternal Include="@(WasmAssembliesToBundle->Distinct())" /> - - - <_EMSDKMissingPaths Condition="'$(_EMSDKMissingPaths)' == '' and ('$(EmscriptenSdkToolsPath)' == '' or !Exists('$(EmscriptenSdkToolsPath)'))">%24(EmscriptenSdkToolsPath)=$(EmscriptenSdkToolsPath) @@ -117,11 +101,27 @@ - + + + + true + + true + false + + + + + true true - false - true + + + false + + + true + false @@ -152,7 +152,7 @@ <_WasmPInvokeHPath>$(_WasmRuntimePackIncludeDir)wasm\pinvoke.h <_DriverGenCPath>$(_WasmIntermediateOutputPath)driver-gen.c - <_DriverGenCNeeded Condition="'$(_DriverGenCNeeded)' == '' and '$(RunAOTCompilation)' == 'true'">true + <_DriverGenCNeeded Condition="'$(_DriverGenCNeeded)' == '' and '$(_WasmShouldAOT)' == 'true'">true <_EmccAssertionLevelDefault>0 <_EmccOptimizationFlagDefault Condition="'$(_WasmDevel)' == 'true'">-O0 -s ASSERTIONS=$(_EmccAssertionLevelDefault) @@ -190,8 +190,8 @@ <_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" /> @@ -236,7 +236,9 @@ + OutputPath="$(_WasmPInvokeTablePath)"> + + @@ -244,10 +246,16 @@ Text="Could not find AOT cross compiler at %24(_MonoAotCrossCompilerPath)=$(_MonoAotCrossCompilerPath)" /> + + + + + OutputPath="$(_WasmICallTablePath)"> + + @@ -263,6 +271,9 @@ + + + @@ -276,13 +287,15 @@ SourceFiles="@(_WasmSourceFileToCompile)" Arguments='"@$(_EmccDefaultFlagsRsp)" "@$(_EmccCompileRsp)"' EnvironmentVariables="@(EmscriptenEnvVars)" - OutputMessageImportance="$(_EmccCompileOutputMessageImportance)" /> + OutputMessageImportance="$(_EmccCompileOutputMessageImportance)"> + + @@ -294,7 +307,9 @@ SourceFiles="@(_BitCodeFile)" Arguments=""@$(_EmccDefaultFlagsRsp)" "@$(_EmccCompileBitcodeRsp)"" EnvironmentVariables="@(EmscriptenEnvVars)" - OutputMessageImportance="$(_EmccCompileOutputMessageImportance)" /> + OutputMessageImportance="$(_EmccCompileOutputMessageImportance)"> + + @@ -303,6 +318,9 @@ <_BitcodeLDFlags Include="$(EmccExtraBitcodeLDFlags)" /> + + + @@ -312,8 +330,8 @@ <_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'" /> @@ -336,6 +354,9 @@ + + + + + + + @@ -358,7 +383,7 @@ - + $(EmccExtraCFlags) -DDRIVER_GEN=1 <_DriverGenCNeeded>true @@ -414,7 +439,7 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_ ******************************* --> - + <_MonoAotCrossCompilerPath>@(MonoAotCrossCompiler->WithMetadataValue('RuntimeIdentifier','browser-wasm')) @@ -433,13 +458,11 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_ - <_AotInputAssemblies Include="@(_WasmAssembliesInternal)" Condition="'%(_WasmAssembliesInternal._InternalForceInterpret)' != 'true'"> + <_AotInputAssemblies Include="@(_WasmAssembliesInternal)"> @(MonoAOTCompilerDefaultAotArguments, ';') @(MonoAOTCompilerDefaultProcessArguments, ';') - <_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 - - <_WasmDedupAssembly>$(_WasmIntermediateOutputPath)\aot-instances.dll - + + LLVMPath="$(EmscriptenUpstreamBinPath)" + IntermediateOutputPath="$(_WasmIntermediateOutputPath)"> - - <_WasmAssembliesInternal Include="@(_AOT_InternalForceInterpretAssemblies)" /> - - <_AOTAssemblies Include="@(_WasmAssembliesInternal)" Condition="'%(_WasmAssembliesInternal._InternalForceInterpret)' != 'true'" /> <_BitcodeFile Include="%(_WasmAssembliesInternal.LlvmBitcodeFile)" /> <_BitcodeFile ObjectFile="$(_WasmIntermediateOutputPath)%(FileName).o" /> - + <_WasmStrippedAssembliesPath>$([MSBuild]::NormalizeDirectory($(_WasmIntermediateOutputPath), 'stripped-assemblies')) + <_AOTedAssemblies Include="@(_WasmAssembliesInternal)" /> <_WasmStrippedAssemblies - Condition="'%(_WasmAssembliesInternal._InternalForceInterpret)' != 'true'" - Include="@(_WasmAssembliesInternal->'$(_WasmStrippedAssembliesPath)%(FileName)%(Extension)')" + Include="@(_AOTedAssemblies)" OriginalPath="%(_WasmAssembliesInternal.Identity)" /> - <_WasmInterpOnlyAssembly Include="@(_WasmAssembliesInternal->WithMetadataValue('_InternalForceInterpret', 'true'))" /> @@ -531,7 +547,7 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_ <_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)" /> - <_WasmAssembliesInternal Include="@(_WasmStrippedAssemblies);@(_WasmInterpOnlyAssembly)" /> + <_WasmAssembliesInternal Include="@(_WasmStrippedAssemblies)" /> diff --git a/src/mono/wasm/build/WasmApp.props b/src/mono/wasm/build/WasmApp.props index bc45a6d54d7..7fdde27c4af 100644 --- a/src/mono/wasm/build/WasmApp.props +++ b/src/mono/wasm/build/WasmApp.props @@ -5,8 +5,7 @@ browser-wasm true - Publish - + <_WasmBuildCoreDependsOn> _InitializeCommonProperties; _BeforeWasmBuildApp; _WasmResolveReferences; @@ -15,6 +14,16 @@ _WasmBuildNativeCore; _WasmGenerateAppBundle; _AfterWasmBuildApp + + + + _PrepareForAfterBuild; + $(_WasmBuildCoreDependsOn) + + + _PrepareForNestedPublish; + $(_WasmBuildCoreDependsOn) + diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index e5ceb13def2..9c96d759a22 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -5,9 +5,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_WasmRuntimeConfigFilePath Condition="$([System.String]::new(%(PublishItemsOutputGroupOutputs.Identity)).EndsWith('$(AssemblyName).runtimeconfig.json'))">@(PublishItemsOutputGroupOutputs) + + + + + + - - + + <_WasmRuntimeConfigFilePath Condition="$([System.String]::new(%(PublishItemsOutputGroupOutputs.Identity)).EndsWith('$(AssemblyName).runtimeconfig.json'))">@(PublishItemsOutputGroupOutputs) + + + + - + + $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'include')) <_WasmRuntimePackSrcDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'src')) - <_WasmIntermediateOutputPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm')) + <_WasmIntermediateOutputPath Condition="'$(WasmBuildingForNestedPublish)' == ''">$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm', 'for-build')) + <_WasmIntermediateOutputPath Condition="'$(WasmBuildingForNestedPublish)' != ''">$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm', 'for-publish')) <_DriverGenCPath>$(_WasmIntermediateOutputPath)driver-gen.c + <_WasmShouldAOT Condition="'$(WasmBuildingForNestedPublish)' == 'true' and '$(RunAOTCompilation)' == 'true'">true + <_WasmShouldAOT Condition="'$(RunAOTCompilationAfterBuild)' == 'true' and '$(RunAOTCompilation)' == 'true'">true + <_WasmShouldAOT Condition="'$(_WasmShouldAOT)' == ''">false + + + + + + - true + true + false $([MSBuild]::NormalizeDirectory($(OutputPath), 'AppBundle')) $(TargetFileName) $([MSBuild]::NormalizeDirectory($(WasmAppDir))) <_MainAssemblyPath Condition="'%(WasmAssembliesToBundle.FileName)' == $(AssemblyName) and '%(WasmAssembliesToBundle.Extension)' == '.dll' and $(WasmGenerateAppBundle) == 'true'">%(WasmAssembliesToBundle.Identity) - <_WasmRuntimeConfigFilePath Condition="$(_MainAssemblyPath) != ''">$([System.IO.Path]::ChangeExtension($(_MainAssemblyPath), '.runtimeconfig.json')) - <_ParsedRuntimeConfigFilePath Condition="'$(_MainAssemblyPath)' != ''">$([System.IO.Path]::GetDirectoryName($(_MainAssemblyPath)))\runtimeconfig.bin + <_WasmRuntimeConfigFilePath Condition="'$(_WasmRuntimeConfigFilePath)' == '' and $(_MainAssemblyPath) != ''">$([System.IO.Path]::ChangeExtension($(_MainAssemblyPath), '.runtimeconfig.json')) + <_ParsedRuntimeConfigFilePath Condition="'$(_WasmRuntimeConfigFilePath)' != ''">$([System.IO.Path]::GetDirectoryName($(_WasmRuntimeConfigFilePath)))\runtimeconfig.bin - + + <_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)" /> <_WasmAssembliesInternal Include="@(WasmAssembliesToBundle->Distinct())" /> + <_WasmSatelliteAssemblies Remove="@(_WasmSatelliteAssemblies)" /> <_WasmSatelliteAssemblies Include="@(_WasmAssembliesInternal)" /> <_WasmSatelliteAssemblies Remove="@(_WasmSatelliteAssemblies)" Condition="!$([System.String]::Copy('%(Identity)').EndsWith('.resources.dll'))" /> @@ -166,9 +244,7 @@ - - - + icudt.dat @@ -184,7 +260,16 @@ + + + + + + + diff --git a/src/mono/wasm/data/aot-tests/ProxyProjectForAOTOnHelix.proj b/src/mono/wasm/data/aot-tests/ProxyProjectForAOTOnHelix.proj index 3a7b66fac73..65a0df19fb6 100644 --- a/src/mono/wasm/data/aot-tests/ProxyProjectForAOTOnHelix.proj +++ b/src/mono/wasm/data/aot-tests/ProxyProjectForAOTOnHelix.proj @@ -5,6 +5,7 @@ $(MSBuildThisFileDirectory)..\wasm_build\ true + true $(TestRootDir)..\publish\ $(OriginalPublishDir)..\extraFiles\ $(TestRootDir)\obj\ diff --git a/src/mono/wasm/debugger/tests/Directory.Build.props b/src/mono/wasm/debugger/tests/Directory.Build.props index 0cff1f10a52..ff8864839da 100644 --- a/src/mono/wasm/debugger/tests/Directory.Build.props +++ b/src/mono/wasm/debugger/tests/Directory.Build.props @@ -4,6 +4,7 @@ $(AspNetCoreAppCurrent) Library + true Debug Release diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj index 0503feaaff0..bf098dd83f5 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj @@ -5,6 +5,7 @@ true false PrepareForWasmBuildApp;$(WasmBuildAppDependsOn) + true diff --git a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs index 71ae231a0fb..4ee7389c04d 100644 --- a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs +++ b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs @@ -191,11 +191,15 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task /// public string? CacheFilePath { get; set; } + [Required] + public string IntermediateOutputPath { get; set; } = string.Empty; + [Output] public string[]? FileWrites { get; private set; } private List _fileWrites = new(); + private IList? _assembliesToCompile; private ConcurrentDictionary 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 override bool Execute() return false; } + if (!Directory.Exists(IntermediateOutputPath)) + Directory.CreateDirectory(IntermediateOutputPath); + if (AotProfilePath != null) { foreach (var path in AotProfilePath) @@ -246,7 +253,7 @@ public override bool Execute() { 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 override bool Execute() 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 override bool Execute() 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 override bool Execute() 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 override bool Execute() //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 override bool Execute() 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 override bool Execute() 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 EnsureAndGetAssembliesInTheSameDir(ITaskItem[] originalAssemblies) + { + List 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 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(); @@ -567,38 +650,20 @@ private bool PrecompileLibrary(ITaskItem assemblyItem, string? monoPaths) // values, which wont work. processArgs.Add($"\"--aot={string.Join(",", aotArgs)}\""); - string paths = ""; if (isDedup) { - StringBuilder sb = new StringBuilder(); - HashSet allPaths = new HashSet(); - 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 { - {"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 @@ private void PrecompileLibraryParallel(ITaskItem assemblyItem, string? monoPaths state.Break(); } - private bool GenerateAotModulesTable(ITaskItem[] assemblies, string[]? profilers, string outputFile) + private bool GenerateAotModulesTable(IEnumerable assemblies, string[]? profilers, string outputFile) { var symbols = new List(); foreach (var asm in assemblies) @@ -831,12 +896,12 @@ private bool TryGetAssemblyName(string asmPath, [NotNullWhen(true)] out string? } } - private IList ConvertAssembliesDictToOrderedList(ConcurrentDictionary dict, ITaskItem[] items) + private static IList ConvertAssembliesDictToOrderedList(ConcurrentDictionary dict, IList originalAssemblies) { - List outItems = new(items.Length); - foreach (ITaskItem item in items) + List 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 @@ public bool CopyOutputFileIfChanged() 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); + } } } diff --git a/src/tasks/Common/Utils.cs b/src/tasks/Common/Utils.cs index 2c1a8b49370..2c624214937 100644 --- a/src/tasks/Common/Utils.cs +++ b/src/tasks/Common/Utils.cs @@ -212,7 +212,7 @@ public static bool CopyIfDifferent(string src, string dst, bool useHash) 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) diff --git a/src/tasks/WasmAppBuilder/EmccCompile.cs b/src/tasks/WasmAppBuilder/EmccCompile.cs index e869e98e856..a5d6af6c3b4 100644 --- a/src/tasks/WasmAppBuilder/EmccCompile.cs +++ b/src/tasks/WasmAppBuilder/EmccCompile.cs @@ -44,6 +44,19 @@ public class EmccCompile : Microsoft.Build.Utilities.Task private int _numCompiled; public override bool Execute() + { + try + { + return ExecuteActual(); + } + catch (LogAsErrorException laee) + { + Log.LogError(laee.Message); + return false; + } + } + + private bool ExecuteActual() { if (SourceFiles.Length == 0) { @@ -133,7 +146,7 @@ public override bool Execute() }); 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 @@ bool ProcessSourceFile(string srcFile, string objFile) 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 @@ private bool ShouldCompile(string srcFile, string objFile, string[] depFiles, ou 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) diff --git a/src/tasks/WasmAppBuilder/IcallTableGenerator.cs b/src/tasks/WasmAppBuilder/IcallTableGenerator.cs index 13c75c39bc6..cc4d8af1ac7 100644 --- a/src/tasks/WasmAppBuilder/IcallTableGenerator.cs +++ b/src/tasks/WasmAppBuilder/IcallTableGenerator.cs @@ -23,6 +23,9 @@ public class IcallTableGenerator : Task [Required, NotNull] public string? OutputPath { get; set; } + [Output] + public string? FileWrites { get; private set; } = ""; + private List _icalls = new List (); private Dictionary _runtimeIcalls = new Dictionary (); @@ -58,6 +61,7 @@ public void GenIcallTable(string runtimeIcallTableFile, string[] assemblies) 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); } diff --git a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs index f6b89533f98..825de67d6e2 100644 --- a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs +++ b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs @@ -16,20 +16,43 @@ 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 void GenPInvokeTable(string[] pinvokeModules, string[] assemblies) 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 @@ private static bool IsBlittable (Type type) 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 diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 57cb7c383f1..58b4b9259e6 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -117,11 +117,24 @@ private class IcuData : AssetEntry } 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 override bool Execute () } FileCopyChecked(MainJS!, Path.Combine(AppDir, "runtime.js"), string.Empty); - var html = @""; - File.WriteAllText(Path.Combine(AppDir, "index.html"), html); + string indexHtmlPath = Path.Combine(AppDir, "index.html"); + if (!File.Exists(indexHtmlPath)) + { + var html = @""; + File.WriteAllText(indexHtmlPath, html); + } foreach (var assembly in _assemblies) { diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj index 6b8813e0aa0..e51e138961e 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj @@ -16,6 +16,7 @@ + diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs new file mode 100644 index 00000000000..687429a3fca --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs @@ -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: "true"); + + // 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 : "true"); + + // 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: "true"); + + 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 = @$" + + + + + + "; + string projectFile = Path.Combine(_projectDir!, $"{id}.csproj"); + AddItemsPropertiesToProject(projectFile, extraItems: extraItems); + + return projectFile; + } + + } + + public enum NativeFilesType { FromRuntimePack, Relinked, AOT }; +} diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs index 9b7709098ed..6c7e3ed788b 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs @@ -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 @@ public BlazorWasmTests(ITestOutputHelper output, SharedBuildPerTestClassFixture { } - // 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 @@ public void NativeRef_EmitsWarningBecauseItRequiresWorkload(string config) { CommandResult res = PublishForRequiresWorkloadTest(config, extraItems: ""); 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 @@ public void AOT_FailsBecauseItRequiresWorkload(string config) [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: "true", @@ -84,18 +54,13 @@ public void AOT_And_NativeRef_FailsBecauseItRequireWorkload(string config) 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"), ""); - File.WriteAllText(Path.Combine(_projectDir!, "Directory.Build.targets"), ""); + 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 @@ private void BuildNet50Project(string config, bool aot, bool expectError, string InitBlazorWasmProjectDir(id); string directoryBuildTargets = @" - + "; @@ -152,11 +117,11 @@ private void BuildNet50Project(string config, bool aot, bool expectError, string 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 @@ private void BuildNet50Project(string config, bool aot, bool expectError, string { 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 } } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildAndRunAttribute.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildAndRunAttribute.cs index c49b19517b3..8d661533265 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildAndRunAttribute.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildAndRunAttribute.cs @@ -34,9 +34,9 @@ public BuildAndRunAttribute(BuildArgs buildArgs, RunHost host = RunHost.All, par .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(); diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs index 8e9be9ee967..b1de19ba169 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildEnvironment.cs @@ -135,5 +135,8 @@ public BuildEnvironment() 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 index 00000000000..42eb96633f9 --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildPublishTests.cs @@ -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"); + + // 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); + } + + } +} diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs index 50425f9844c..b6fab5c6c43 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs @@ -10,6 +10,7 @@ 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 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp 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 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp } 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 @@ public void InitBlazorWasmProjectDir(string id) 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 @@ static void AssertRuntimePackPath(string buildOutput) 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 @@ protected static void AssertFile(string file0, string file1, string? label=null, 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 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 @@ void LogData(string label, string? message) } } - 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 @@ private static string GetEnvironmentVariableOrDefault(string envVarName, string 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 StatFiles(IEnumerable fullpaths) + { + Dictionary 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 @@ public static int Main() 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 index 00000000000..d2226d5caad --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/CleanTests.cs @@ -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 + true"; + + 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"; + + 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}"); + } +} diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/CommandResult.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/CommandResult.cs index db95f8bab36..7372ef034bc 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/CommandResult.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/CommandResult.cs @@ -24,10 +24,10 @@ public CommandResult(ProcessStartInfo startInfo, int exitCode, string output) 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 @@ public void EnsureExitCode(int expectedExitCode = 0, string messagePrefix = "", throw new XunitException(message.ToString()); } + + return this; } } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/HelperExtensions.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/HelperExtensions.cs index a70a51bb10b..bff462f1f5d 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/HelperExtensions.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/HelperExtensions.cs @@ -6,6 +6,7 @@ using System.Linq; using System.IO; using System.Text; +using System.Collections; #nullable enable @@ -96,7 +97,9 @@ public static class HelperExtensions public static void UpdateTo(this IDictionary dict, bool unchanged, params string[] filenames) { - foreach (var filename in filenames) + IEnumerable keys = filenames.Length == 0 ? dict.Keys.ToList() : filenames; + + foreach (var filename in keys) { if (!dict.TryGetValue(filename, out var oldValue)) { diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs index 80c7ba5fe85..3453f9922db 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs @@ -25,17 +25,14 @@ public NativeBuildTests(ITestOutputHelper output, SharedBuildPerTestClassFixture [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: "true"); 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); @@ -44,21 +41,46 @@ private void NativeBuild(string projectNamePrefix, string projectContents, Build host: host, id: id); } + [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 = @" + + "; + + string projectName = $"mono_aot_cross_{buildArgs.Config}_{buildArgs.AOT}"; + + buildArgs = buildArgs with { ProjectName = projectName, ExtraBuildArgs = "-p:PublishTrimmed=false -v:n" }; + buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "true", 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 = @" - + - + "; string projectName = $"bc_to_o_{buildArgs.Config}"; @@ -71,9 +93,41 @@ public void IntermediateBitcodeToObjectFilesAreNotLLVMIR(BuildArgs buildArgs, st 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 = @" + + + "; + 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); + } } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs index eb306286ba5..3ccf400ee92 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs @@ -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; diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs index 7ebc2d05cdd..b73e521f8ca 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs @@ -5,12 +5,13 @@ 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 { diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs index 2256fa58386..9047fd062a8 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs @@ -5,6 +5,7 @@ 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 @@ #nullable enable -namespace Wasm.Build.Tests +namespace Wasm.Build.NativeRebuild.Tests { // TODO: test for runtime components public class NativeRebuildTestsBase : BuildTestBase @@ -136,29 +137,6 @@ internal void CompareStat(IDictionary oldStat, IDictionary StatFiles(IEnumerable fullpaths) - { - Dictionary 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 GetFilesTable(BuildArgs buildArgs, BuildPaths paths, bool unchanged) { List files = new() @@ -203,7 +181,4 @@ protected void AssertSubstring(string substring, string full, bool contains) 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); } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs index 1ddbd4467b6..96f6d41fa10 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs @@ -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 { diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/ReferenceNewAssemblyRebuildTest.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/ReferenceNewAssemblyRebuildTest.cs index d908e287cf8..5aa3d2916f3 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/ReferenceNewAssemblyRebuildTest.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/ReferenceNewAssemblyRebuildTest.cs @@ -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 { diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/SimpleSourceChangeRebuildTest.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/SimpleSourceChangeRebuildTest.cs index 7f51447cb9e..bbe7d602216 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/SimpleSourceChangeRebuildTest.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/SimpleSourceChangeRebuildTest.cs @@ -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 { diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/SatelliteAssembliesTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/SatelliteAssembliesTests.cs index 45ce2b37ed2..c3a40b47020 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/SatelliteAssembliesTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/SatelliteAssembliesTests.cs @@ -40,12 +40,13 @@ public SatelliteAssembliesTests(ITestOutputHelper output, SharedBuildPerTestClas 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: $"{(nativeRelink ? "true" : "false")}"); + extraProperties: nativeRelink ? $"true" : string.Empty); BuildProject(buildArgs, initProject: () => diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/SharedBuildPerTestClassFixture.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/SharedBuildPerTestClassFixture.cs index 03437e67302..019391f3efa 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/SharedBuildPerTestClassFixture.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/SharedBuildPerTestClassFixture.cs @@ -16,15 +16,20 @@ public class SharedBuildPerTestClassFixture : IDisposable public Dictionary _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? 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); } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj index 7dacbc3ec26..6c9a7037025 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj @@ -7,7 +7,6 @@ true BuildAndRun xunit - false true false TEST_DEBUG_CONFIG_ALSO @@ -18,7 +17,7 @@ true - + false diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmNativeDefaultsTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmNativeDefaultsTests.cs new file mode 100644 index 00000000000..64d008549e8 --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmNativeDefaultsTests.cs @@ -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", "false", /*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", "true", + // /*aot*/ true, /*build*/ true, /*publish*/ true)] + + /* AOT not affected by trimming */ + [InlineData("Release", "false", /*aot*/ true, /*build*/ false, /*publish*/ true)] + [InlineData("Debug", "false", /*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", "false", /*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 = @$""; + 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"; + + string printValueTarget = @" + + + + "; + + 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; + } + } +} diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets index f77fc96c48d..5cef9821d8f 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Directory.Build.targets @@ -10,7 +10,8 @@ + AfterTargets="ProcessFrameworkReferences" + Condition="'$(WasmNativeWorkload)' == 'true'"> @@ -21,7 +22,8 @@ + AfterTargets="ResolveFrameworkReferences" + Condition="'$(WasmNativeWorkload)' == 'true'"> + AfterTargets="ResolveTargetingPackAssets" + Condition="'$(WasmNativeWorkload)' == 'true'"> @@ -56,7 +59,7 @@ 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 index 00000000000..909ea0382ba --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.props @@ -0,0 +1,11 @@ + + + $(RuntimeSrcDir)\artifacts\bin\ + $([MSBuild]::NormalizeDirectory($(ArtifactsBinDir), 'microsoft.netcore.app.runtime.browser-wasm', $(RuntimeConfig))) + + + + $(WasmBuildSupportDir)\ + $([MSBuild]::NormalizeDirectory($(BuildBaseDir), 'microsoft.netcore.app.runtime.browser-wasm')) + + 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 index 00000000000..639a8413dc3 --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Blazor.Local.Directory.Build.targets @@ -0,0 +1,36 @@ + + + <_MicrosoftNetCoreAppRefDir>$(AppRefDir)\ + + + + + + + + + + + + + + + + + diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Local.Directory.Build.props b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Local.Directory.Build.props index 1a9c112e747..6d53e53b3bf 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Local.Directory.Build.props +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Local.Directory.Build.props @@ -7,8 +7,4 @@ - - - PrepareForWasmBuild;$(WasmBuildAppDependsOn) - diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Local.Directory.Build.targets b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Local.Directory.Build.targets index 65f76e4fc85..b327d1e91f6 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Local.Directory.Build.targets +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Local.Directory.Build.targets @@ -16,12 +16,6 @@ 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" /> - - - - - - diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets index 105c0a74c08..34eeeaeee7b 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets @@ -1,15 +1,8 @@ - PrepareForWasmBuild;$(WasmBuildAppDependsOn) <_MicrosoftNetCoreAppRefDir>$(AppRefDir)\ - - - - - - diff --git a/src/tests/Common/wasm-test-runner/WasmTestRunner.proj b/src/tests/Common/wasm-test-runner/WasmTestRunner.proj index 5f4d4f084d1..e2eda44d7ff 100644 --- a/src/tests/Common/wasm-test-runner/WasmTestRunner.proj +++ b/src/tests/Common/wasm-test-runner/WasmTestRunner.proj @@ -10,14 +10,17 @@ $(MSBuildThisFileDirectory)\obj\$(Configuration)\wasm $(TestBinDir)/WasmApp/ 99.0 + true + true $(CORE_ROOT)\WasmAppBuilder\WasmAppBuilder.dll $(CORE_ROOT)\MonoAOTCompiler\MonoAOTCompiler.dll $(CORE_ROOT)\JsonToItemsTaskFactory\JsonToItemsTaskFactory.dll $(CORE_ROOT)\RuntimeConfigParser\RuntimeConfigParser.dll + BuildApp;$(WasmBuildAppDependsOn) - + $(TestAssemblyFileName) $(AppDir) diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/WebAssembly.Browser.HotReload.Test.csproj b/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/WebAssembly.Browser.HotReload.Test.csproj index 2ba05527c2a..d9dbaa2ebd4 100644 --- a/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/WebAssembly.Browser.HotReload.Test.csproj +++ b/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/WebAssembly.Browser.HotReload.Test.csproj @@ -16,13 +16,10 @@ Always + - - - - diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/WebAssembly.Browser.RuntimeConfig.Test.csproj b/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/WebAssembly.Browser.RuntimeConfig.Test.csproj index 4efa17aab45..35e9eec2cae 100644 --- a/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/WebAssembly.Browser.RuntimeConfig.Test.csproj +++ b/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/WebAssembly.Browser.RuntimeConfig.Test.csproj @@ -5,13 +5,9 @@ 42 runtime.js - + + - - - - - diff --git a/src/tests/FunctionalTests/WebAssembly/Directory.Build.targets b/src/tests/FunctionalTests/WebAssembly/Directory.Build.targets index b0d320fabe3..802f2525e58 100644 --- a/src/tests/FunctionalTests/WebAssembly/Directory.Build.targets +++ b/src/tests/FunctionalTests/WebAssembly/Directory.Build.targets @@ -2,13 +2,6 @@ - PrepareForWasmBuild;$(WasmBuildAppDependsOn) $(OutputPath)\$(Configuration)\AppBundle\ - - - - - - -- GitLab