未验证 提交 4af01264 编写于 作者: A Ankit Jain 提交者: GitHub

[wasm] Add support for native relinking after Build, and… (#59153)

… AOT after publish (#58913)

Forward port #58913

(cherry picked from commit f38d58f1)
上级 16a0ab39
......@@ -164,7 +164,7 @@
<SQLitePCLRawbundle_greenVersion>2.0.4</SQLitePCLRawbundle_greenVersion>
<MoqVersion>4.12.0</MoqVersion>
<FsCheckVersion>2.14.3</FsCheckVersion>
<SdkVersionForWorkloadTesting>6.0.100-rc.2.21425.12</SdkVersionForWorkloadTesting>
<SdkVersionForWorkloadTesting>6.0.100-rc.2.21463.12</SdkVersionForWorkloadTesting>
<!-- Docs -->
<MicrosoftPrivateIntellisenseVersion>6.0.0-preview-20210916.1</MicrosoftPrivateIntellisenseVersion>
<!-- ILLink -->
......
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
......@@ -10,6 +10,7 @@
<PublishingTestsRun>true</PublishingTestsRun>
<BundleTestAppTargets>BundleTestAppleApp;BundleTestAndroidApp</BundleTestAppTargets>
<PublishTestAsSelfContainedDependsOn>Publish</PublishTestAsSelfContainedDependsOn>
</PropertyGroup>
<PropertyGroup Condition="'$(EnableAggressiveTrimming)' == 'true' or '$(EnableSoftTrimming)' == 'true'">
......@@ -111,6 +112,7 @@
OutputType="AsmOnly"
Assemblies="@(AotInputAssemblies)"
AotModulesTablePath="$(BundleDir)\modules.c"
IntermediateOutputPath="$(IntermediateOutputPath)"
UseLLVM="$(MonoEnableLLVM)"
LLVMPath="$(MonoAotCrossDir)">
<Output TaskParameter="CompiledAssemblies" ItemName="BundleAssemblies" />
......@@ -200,6 +202,7 @@
Assemblies="@(AotInputAssemblies)"
AotModulesTablePath="$(BundleDir)\modules.m"
AotModulesTableLanguage="ObjC"
IntermediateOutputPath="$(IntermediateOutputPath)"
UseLLVM="$(MonoEnableLLVM)"
LLVMPath="$(MonoAotCrossDir)">
<Output TaskParameter="CompiledAssemblies" ItemName="BundleAssemblies" />
......@@ -306,7 +309,7 @@
<Target Name="PublishTestAsSelfContained"
Condition="'$(IsCrossTargetingBuild)' != 'true'"
AfterTargets="Build"
DependsOnTargets="Publish;$(BundleTestAppTargets);ArchiveTests" />
DependsOnTargets="$(PublishTestAsSelfContainedDependsOn);$(BundleTestAppTargets);ArchiveTests" />
<Target Name="PrepareForTestUsingWorkloads"
BeforeTargets="Test"
......
<Project>
<!-- We need to set this in order to get extensibility on xunit category traits and other arguments we pass down to xunit via MSBuild properties -->
<PropertyGroup>
<IsWasmProject Condition="'$(IsWasmProject)' == ''">true</IsWasmProject>
<WasmGenerateAppBundle Condition="'$(WasmGenerateAppBundle)' == ''">true</WasmGenerateAppBundle>
<BundleTestAppTargets>$(BundleTestAppTargets);BundleTestWasmApp</BundleTestAppTargets>
<DebuggerSupport Condition="'$(DebuggerSupport)' == '' and '$(Configuration)' == 'Debug'">true</DebuggerSupport>
<!-- Some tests expect to load satellite assemblies by path, eg. System.Runtime.Loader.Tests,
......@@ -55,10 +57,20 @@
Condition="'$(BuildAOTTestsOn)' == 'local'" />
<PropertyGroup>
<WasmBuildAppDependsOn>PrepareForWasmBuildApp;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
<BundleTestWasmAppDependsOn Condition="'$(BuildAOTTestsOn)' == 'local'">WasmTriggerPublishApp</BundleTestWasmAppDependsOn>
<BundleTestWasmAppDependsOn Condition="'$(BuildAOTTestsOnHelix)' == 'true'">$(BundleTestWasmAppDependsOn);_BundleAOTTestWasmAppForHelix</BundleTestWasmAppDependsOn>
</PropertyGroup>
<PropertyGroup Condition="'$(BuildAOTTestsOnHelix)' == 'true'">
<!-- wasm targets are not imported at all, in this case, because we run the wasm build on helix -->
</PropertyGroup>
<PropertyGroup Condition="'$(BuildAOTTestsOnHelix)' != 'true'">
<WasmBuildOnlyAfterPublish>true</WasmBuildOnlyAfterPublish>
<BundleTestWasmAppDependsOn Condition="'$(BuildAOTTestsOn)' == 'local'">WasmBuildApp</BundleTestWasmAppDependsOn>
<BundleTestWasmAppDependsOn Condition="'$(BuildAOTTestsOnHelix)' == 'true'">$(BundleTestWasmAppDependsOn);_BundleAOTTestWasmAppForHelix</BundleTestWasmAppDependsOn>
<!-- wasm's publish targets will trigger publish, so we shouldn't do that -->
<PublishTestAsSelfContainedDependsOn />
<WasmNestedPublishAppDependsOn>PrepareForWasmBuildApp;$(WasmNestedPublishAppDependsOn)</WasmNestedPublishAppDependsOn>
</PropertyGroup>
<ItemGroup>
......@@ -80,6 +92,8 @@
<RuntimeConfigFilePath>$([System.IO.Path]::ChangeExtension($(_MainAssemblyPath), '.runtimeconfig.json'))</RuntimeConfigFilePath>
</PropertyGroup>
<Error Text="Item WasmAssembliesToBundle is empty. This is likely an authoring error." Condition="@(WasmAssembliesToBundle->Count()) == 0" />
<ItemGroup>
<BundleFiles Include="$(WasmMainJSPath)" TargetDir="publish" />
<BundleFiles Include="@(WasmAssembliesToBundle)" TargetDir="publish\%(WasmAssembliesToBundle.RecursiveDir)" />
......@@ -154,7 +168,7 @@
</ItemGroup>
<ItemGroup>
<WasmAssembliesToBundle Include="$(PublishDir)\**\*.dll"/>
<WasmAssembliesToBundle Include="$(PublishDir)\**\*.dll" Condition="'$(BuildAOTTestsOnHelix)' == 'true'" />
<WasmFilesToIncludeInFileSystem Include="@(ContentWithTargetPath)" />
<_CopyLocalPaths
......
......@@ -426,10 +426,10 @@
</PropertyGroup>
<ItemGroup Condition="'$(Scenario)' == 'BuildWasmApps'">
<HelixWorkItem Include="@(BuildWasmApps_PerJobList->'$(WorkItemPrefix)%(FileName)')">
<HelixWorkItem Include="@(BuildWasmApps_PerJobList->'$(WorkItemPrefix)%(Extension)')">
<PayloadArchive>$(_BuildWasmAppsPayloadArchive)</PayloadArchive>
<PreCommands Condition="'$(OS)' == 'Windows_NT'">set &quot;HELIX_XUNIT_ARGS=-class Wasm.Build.Tests.%(Identity)&quot;</PreCommands>
<PreCommands Condition="'$(OS)' != 'Windows_NT'">export &quot;HELIX_XUNIT_ARGS=-class Wasm.Build.Tests.%(Identity)&quot;</PreCommands>
<PreCommands Condition="'$(OS)' == 'Windows_NT'">set &quot;HELIX_XUNIT_ARGS=-class %(Identity)&quot;</PreCommands>
<PreCommands Condition="'$(OS)' != 'Windows_NT'">export &quot;HELIX_XUNIT_ARGS=-class %(Identity)&quot;</PreCommands>
<Command>$(HelixCommand)</Command>
<Timeout>$(_workItemTimeout)</Timeout>
</HelixWorkItem>
......
......@@ -6,7 +6,7 @@
<Error Text="%24(SdkWithWorkloadForTestingPath) is not set" Condition="'$(SdkWithWorkloadForTestingPath)' == ''" />
<Error Text="%24(SdkVersionForWorkloadTesting) is not set" Condition="'$(SdkVersionForWorkloadTesting)' == ''" />
<Message Text="** Installing sdk $(SdkWithWorkloadForTestingPath) for workload based tests into $(SdkWithWorkloadForTestingPath)" Importance="High" />
<Message Text="** Installing sdk $(SdkVersionForWorkloadTesting) for workload based tests into $(SdkWithWorkloadForTestingPath)" Importance="High" />
<RemoveDir Directories="$(SdkWithWorkloadForTestingPath)" />
<MakeDir Directories="$(SdkWithWorkloadForTestingPath)" />
......@@ -16,6 +16,8 @@
</ItemGroup>
<Copy SourceFiles="@(_SourceFiles)" DestinationFolder="$(SdkWithWorkloadForTestingPath)\%(_SourceFiles.RecursiveDir)" />
<Copy SourceFiles="$(MonoProjectRoot)\wasm\BlazorOverwrite.targets" DestinationFiles="$(SdkWithWorkloadForTestingPath)\sdk\$(SdkVersionForWorkloadTesting)\Sdks\Microsoft.NET.Sdk.BlazorWebAssembly\targets\Microsoft.NET.Sdk.BlazorWebAssembly.6_0.targets" />
<WriteLinesToFile File="$(SdkWithWorkloadStampPath)" Lines="" Overwrite="true" />
</Target>
......@@ -23,7 +25,7 @@
<Error Text="%24(SdkWithNoWorkloadForTestingPath) is not set" Condition="'$(SdkWithNoWorkloadForTestingPath)' == ''" />
<Error Text="%24(SdkVersionForWorkloadTesting) is not set" Condition="'$(SdkVersionForWorkloadTesting)' == ''" />
<Message Text="** Installing sdk $(SdkWithNoWorkloadForTestingPath) for workload based tests into $(SdkWithNoWorkloadForTestingPath)" Importance="High" />
<Message Text="** Installing sdk $(SdkVersionForWorkloadTesting) for workload based tests into $(SdkWithNoWorkloadForTestingPath)" Importance="High" />
<RemoveDir Directories="$(SdkWithNoWorkloadForTestingPath)" />
<MakeDir Directories="$(SdkWithNoWorkloadForTestingPath)" />
......@@ -39,6 +41,7 @@
<Exec Condition="$([MSBuild]::IsOSPlatform('windows'))"
Command='powershell -ExecutionPolicy ByPass -NoProfile -command "&amp; $(_DotNetInstallScriptPath) -InstallDir $(SdkWithNoWorkloadForTestingPath) -Version $(SdkVersionForWorkloadTesting)"' />
<Copy SourceFiles="$(MonoProjectRoot)\wasm\BlazorOverwrite.targets" DestinationFiles="$(SdkWithNoWorkloadForTestingPath)\sdk\$(SdkVersionForWorkloadTesting)\Sdks\Microsoft.NET.Sdk.BlazorWebAssembly\targets\Microsoft.NET.Sdk.BlazorWebAssembly.6_0.targets" />
<WriteLinesToFile File="$(SdkWithNoWorkloadStampPath)" Lines="" Overwrite="true" />
</Target>
......
......@@ -72,6 +72,7 @@
AotModulesTablePath="$(_AotModulesTablePath)"
ToolPrefix="$(_AotToolPrefix)"
LibraryFormat="$(_AotLibraryFormat)"
IntermediateOutputPath="$(IntermediateOutputPath)"
UseLLVM="$(UseLLVM)"
LLVMPath="$(MonoAotCrossDir)">
<Output TaskParameter="CompiledAssemblies" ItemName="BundleAssemblies" />
......
......@@ -60,6 +60,7 @@
AotModulesTablePath="$(AppDir)\modules.m"
AotModulesTableLanguage="ObjC"
OutputDir="$(PublishDir)"
IntermediateOutputPath="$(IntermediateOutputPath)"
UseLLVM="$(UseLLVM)"
LLVMPath="$(MonoAotCrossDir)">
<Output TaskParameter="CompiledAssemblies" ItemName="BundleAssemblies" />
......
......@@ -31,7 +31,6 @@
<Target Name="PrepareDeltasForWasmApp" DependsOnTargets="Build;CompileDiff;ComputeDeltaFileOutputNames">
<ItemGroup>
<WasmAssembliesToBundle Include="$(TargetDir)publish\*.dll" />
<WasmFilesToIncludeInFileSystem Include="@(_DeltaFileForPublish)">
<TargetPath>\%(_DeltaFileForPublish.Filename)%(_DeltaFileForPublish.Extension)</TargetPath>
</WasmFilesToIncludeInFileSystem>
......
<Project>
<Import Project="../Directory.Build.targets" />
<Target Name="PrepareForWasmBuild" BeforeTargets="WasmBuildApp">
<ItemGroup>
<WasmAssembliesToBundle Include="$(TargetDir)publish\*.dll" />
</ItemGroup>
</Target>
<Import Project="$(MonoProjectRoot)\wasm\build\WasmApp.InTree.targets" />
<Target Name="BuildSampleInTree"
......
......@@ -15,4 +15,4 @@ public static async Task<int> Main(string[] args)
}
return args.Length;
}
}
\ No newline at end of file
}
......@@ -10,9 +10,12 @@ CONFIG?=Release
WASM_DEFAULT_BUILD_ARGS?=/p:TargetArchitecture=wasm /p:TargetOS=Browser /p:Configuration=$(CONFIG)
all: build
all: publish
build:
EMSDK_PATH=$(realpath $(TOP)/src/mono/wasm/emsdk) $(DOTNET) build $(DOTNET_Q_ARGS) $(WASM_DEFAULT_BUILD_ARGS) $(MSBUILD_ARGS) $(PROJECT_NAME)
publish:
EMSDK_PATH=$(realpath $(TOP)/src/mono/wasm/emsdk) $(DOTNET) publish $(DOTNET_Q_ARGS) $(WASM_DEFAULT_BUILD_ARGS) $(MSBUILD_ARGS) $(PROJECT_NAME)
clean:
......
此差异已折叠。
# 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`
......@@ -17,7 +17,7 @@
Condition="'$(_LocalMicrosoftNetCoreAppRuntimePackDir)' != '' and
'%(ResolvedRuntimePack.FrameworkName)' == 'Microsoft.NETCore.App'" />
</ItemGroup>
<Message Text="Used runtime pack: %(ResolvedRuntimePack.PackageDirectory)" Importance="high" />
<Message Text="Used runtime pack: %(ResolvedRuntimePack.PackageDirectory) for $(MSBuildProjectName)" Importance="normal" />
</Target>
<Target Name="RebuildWasmAppBuilder">
......@@ -35,9 +35,9 @@
</Target>
<Target Name="CopyAppZipToHelixTestDir"
Condition="'$(WasmCopyAppZipToHelixTestDir)' == 'true'"
Condition="'$(WasmCopyAppZipToHelixTestDir)' == 'true' and '$(WasmBuildingForNestedPublish)' != 'true'"
AfterTargets="Build"
DependsOnTargets="Publish">
DependsOnTargets="WasmTriggerPublishApp">
<PropertyGroup>
<WasmHelixTestAppRelativeDir Condition="'$(WasmHelixTestAppRelativeDir)' == ''">$(MSBuildProjectName)</WasmHelixTestAppRelativeDir>
<!-- Helix properties -->
......
......@@ -25,7 +25,7 @@
<UsingTask TaskName="RuntimeConfigParserTask" AssemblyFile="$(RuntimeConfigParserTasksAssemblyPath)" />
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
<PublishTrimmed Condition="'$(PublishTrimmed)' == ''">true</PublishTrimmed>
<TrimMode>link</TrimMode>
</PropertyGroup>
......@@ -45,7 +45,7 @@
Condition="'$(MicrosoftNetCoreAppRuntimePackLocationToUse)' != '' and
'%(ResolvedRuntimePack.FrameworkName)' == 'Microsoft.NETCore.App'" />
</ItemGroup>
<Message Text="Used runtime pack: %(ResolvedRuntimePack.PackageDirectory)" Importance="high" />
<Message Text="Used runtime pack: %(ResolvedRuntimePack.PackageDirectory) for $(MSBuildProjectName)" Importance="normal" />
</Target>
<!-- the actual properties need to get set in the props, so because UsingTasks depend on those. -->
......
......@@ -17,12 +17,6 @@
_CompleteWasmBuildNative
</_WasmBuildNativeCoreDependsOn>
<WasmBuildNativeOnlyDependsOn>
_InitializeCommonProperties;
_PrepareForWasmBuildNativeOnly;
_WasmBuildNativeCore;
</WasmBuildNativeOnlyDependsOn>
<_BeforeWasmBuildAppDependsOn>
$(_BeforeWasmBuildAppDependsOn);
_SetupEmscripten;
......@@ -39,16 +33,6 @@
<Import Project="$(MSBuildThisFileDirectory)EmSdkRepo.Defaults.props" Condition="'$(WasmUseEMSDK_PATH)' == 'true'" />
<!-- "public" target meant for use outside the regular wasm app generation process FIXME: rename please! -->
<Target Name="WasmBuildNativeOnly" DependsOnTargets="$(WasmBuildNativeOnlyDependsOn)" Condition="'$(WasmBuildNative)' == 'true'" />
<Target Name="_PrepareForWasmBuildNativeOnly">
<ItemGroup>
<_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)" />
<_WasmAssembliesInternal Include="@(WasmAssembliesToBundle->Distinct())" />
</ItemGroup>
</Target>
<Target Name="_SetupEmscripten">
<PropertyGroup>
<_EMSDKMissingPaths Condition="'$(_EMSDKMissingPaths)' == '' and ('$(EmscriptenSdkToolsPath)' == '' or !Exists('$(EmscriptenSdkToolsPath)'))">%24(EmscriptenSdkToolsPath)=$(EmscriptenSdkToolsPath) </_EMSDKMissingPaths>
......@@ -117,11 +101,27 @@
<Error Condition="'$(RunAOTCompilation)' == 'true' and '$(_IsEMSDKMissing)' == 'true'"
Text="$(_EMSDKMissingErrorMessage) Emscripten SDK is required for AOT'ing assemblies." />
<PropertyGroup>
<!-- When Building -->
<PropertyGroup Condition="'$(WasmBuildingForNestedPublish)' != 'true'">
<!-- build AOT, only if explicitly requested -->
<WasmBuildNative Condition="'$(RunAOTCompilation)' == 'true' and '$(RunAOTCompilationAfterBuild)' == 'true'">true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and @(NativeFileReference->Count()) > 0" >true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == ''">false</WasmBuildNative>
</PropertyGroup>
<!-- When Publishing -->
<PropertyGroup Condition="'$(WasmBuildingForNestedPublish)' == 'true'">
<!-- AOT==true overrides WasmBuildNative -->
<WasmBuildNative Condition="'$(RunAOTCompilation)' == 'true'">true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and @(NativeFileReference->Count()) > 0" >true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(PublishTrimmed)' != 'true'" >false</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(Configuration)' == 'Release'" >true</WasmBuildNative>
<!-- not aot, not trimmed app, no reason to relink -->
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(PublishTrimmed)' != 'true'">false</WasmBuildNative>
<!-- default to relinking in Release config -->
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(Configuration)' == 'Release'">true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == ''">false</WasmBuildNative>
</PropertyGroup>
......@@ -152,7 +152,7 @@
<_WasmPInvokeHPath>$(_WasmRuntimePackIncludeDir)wasm\pinvoke.h</_WasmPInvokeHPath>
<_DriverGenCPath>$(_WasmIntermediateOutputPath)driver-gen.c</_DriverGenCPath>
<_DriverGenCNeeded Condition="'$(_DriverGenCNeeded)' == '' and '$(RunAOTCompilation)' == 'true'">true</_DriverGenCNeeded>
<_DriverGenCNeeded Condition="'$(_DriverGenCNeeded)' == '' and '$(_WasmShouldAOT)' == 'true'">true</_DriverGenCNeeded>
<_EmccAssertionLevelDefault>0</_EmccAssertionLevelDefault>
<_EmccOptimizationFlagDefault Condition="'$(_WasmDevel)' == 'true'">-O0 -s ASSERTIONS=$(_EmccAssertionLevelDefault)</_EmccOptimizationFlagDefault>
......@@ -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 @@
<PInvokeTableGenerator
Modules="@(_WasmPInvokeModules)"
Assemblies="@(_WasmAssembliesInternal)"
OutputPath="$(_WasmPInvokeTablePath)" />
OutputPath="$(_WasmPInvokeTablePath)">
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
</PInvokeTableGenerator>
</Target>
<Target Name="_GenerateICallTable" Condition="'$(WasmLinkIcalls)' == 'true'">
......@@ -244,10 +246,16 @@
Text="Could not find AOT cross compiler at %24(_MonoAotCrossCompilerPath)=$(_MonoAotCrossCompilerPath)" />
<Exec Command='"$(_MonoAotCrossCompilerPath)" --print-icall-table > "$(_WasmRuntimeICallTablePath)"' />
<ItemGroup>
<FileWrites Include="$(_WasmRuntimeICallTablePath)" />
</ItemGroup>
<IcallTableGenerator
RuntimeIcallTableFile="$(_WasmRuntimeICallTablePath)"
Assemblies="@(_WasmAssembliesInternal)"
OutputPath="$(_WasmICallTablePath)" />
OutputPath="$(_WasmICallTablePath)">
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
</IcallTableGenerator>
</Target>
<Target Name="_WasmSelectRuntimeComponentsForLinking" Condition="'$(WasmNativeWorkload)' == true" DependsOnTargets="_MonoSelectRuntimeComponents" />
......@@ -263,6 +271,9 @@
</ItemGroup>
<WriteLinesToFile Lines="@(_EmccCFlags)" File="$(_EmccCompileRsp)" Overwrite="true" WriteOnlyWhenDifferent="true" />
<ItemGroup>
<FileWrites Include="$(_EmccCompileRsp)" />
</ItemGroup>
<!-- warm up the cache -->
<Exec Command="$(_EmBuilder) build MINIMAL" EnvironmentVariables="@(EmscriptenEnvVars)" StandardOutputImportance="Low" StandardErrorImportance="Low" />
......@@ -276,13 +287,15 @@
SourceFiles="@(_WasmSourceFileToCompile)"
Arguments='"@$(_EmccDefaultFlagsRsp)" "@$(_EmccCompileRsp)"'
EnvironmentVariables="@(EmscriptenEnvVars)"
OutputMessageImportance="$(_EmccCompileOutputMessageImportance)" />
OutputMessageImportance="$(_EmccCompileOutputMessageImportance)">
<Output TaskParameter="OutputFiles" ItemName="FileWrites" />
</EmccCompile>
</Target>
<Target Name="_WasmCompileAssemblyBitCodeFilesForAOT"
Inputs="@(_BitcodeFile);$(_EmccDefaultFlagsRsp);$(_EmccCompileBitcodeRsp)"
Outputs="@(_BitcodeFile->'%(ObjectFile)')"
Condition="'$(RunAOTCompilation)' == 'true' and @(_BitcodeFile->Count()) > 0"
Condition="'$(_WasmShouldAOT)' == 'true' and @(_BitcodeFile->Count()) > 0"
DependsOnTargets="_WasmWriteRspForCompilingBitcode">
<ItemGroup>
......@@ -294,7 +307,9 @@
SourceFiles="@(_BitCodeFile)"
Arguments="&quot;@$(_EmccDefaultFlagsRsp)&quot; &quot;@$(_EmccCompileBitcodeRsp)&quot;"
EnvironmentVariables="@(EmscriptenEnvVars)"
OutputMessageImportance="$(_EmccCompileOutputMessageImportance)" />
OutputMessageImportance="$(_EmccCompileOutputMessageImportance)">
<Output TaskParameter="OutputFiles" ItemName="FileWrites" />
</EmccCompile>
</Target>
<Target Name="_WasmWriteRspForCompilingBitcode">
......@@ -303,6 +318,9 @@
<_BitcodeLDFlags Include="$(EmccExtraBitcodeLDFlags)" />
</ItemGroup>
<WriteLinesToFile Lines="@(_BitcodeLDFlags)" File="$(_EmccCompileBitcodeRsp)" Overwrite="true" WriteOnlyWhenDifferent="true" />
<ItemGroup>
<FileWrites Include="$(_EmccCompileBitcodeRsp)" />
</ItemGroup>
</Target>
<Target Name="_WasmWriteRspFilesForLinking">
......@@ -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 @@
</ItemGroup>
<WriteLinesToFile Lines="@(_EmccLinkStepArgs)" File="$(_EmccLinkRsp)" Overwrite="true" WriteOnlyWhenDifferent="true" />
<ItemGroup>
<FileWrites Include="$(_EmccLinkRsp)" />
</ItemGroup>
</Target>
<Target Name="_WasmLinkDotNet"
......@@ -346,6 +367,10 @@
<Message Text="Linking with emcc. This may take a while ..." Importance="High" />
<Message Text="Running emcc with @(_EmccLinkStepArgs->'%(Identity)', ' ')" Importance="Low" />
<Exec Command='emcc "@$(_EmccDefaultFlagsRsp)" "@$(_EmccLinkRsp)"' EnvironmentVariables="@(EmscriptenEnvVars)" />
<ItemGroup>
<FileWrites Include="$(_WasmIntermediateOutputPath)dotnet.wasm" />
<FileWrites Include="$(_WasmIntermediateOutputPath)dotnet.js" />
</ItemGroup>
<Message Text="Optimizing dotnet.wasm ..." Importance="High" />
<Exec Command='wasm-opt$(_ExeExt) --strip-dwarf "$(_WasmIntermediateOutputPath)dotnet.wasm" -o "$(_WasmIntermediateOutputPath)dotnet.wasm"' Condition="'$(WasmNativeStrip)' == 'true'" IgnoreStandardErrorWarningFormat="true" EnvironmentVariables="@(EmscriptenEnvVars)" />
......@@ -358,7 +383,7 @@
</ItemGroup>
</Target>
<Target Name="_GenerateDriverGenC" Condition="'$(RunAOTCompilation)' != 'true' and '$(WasmProfilers)' != ''">
<Target Name="_GenerateDriverGenC" Condition="'$(_WasmShouldAOT)' != 'true' and '$(WasmProfilers)' != ''">
<PropertyGroup>
<EmccExtraCFlags>$(EmccExtraCFlags) -DDRIVER_GEN=1</EmccExtraCFlags>
<_DriverGenCNeeded>true</_DriverGenCNeeded>
......@@ -414,7 +439,7 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
*******************************
-->
<Target Name="_WasmAotCompileApp" Condition="'$(RunAOTCompilation)' == 'true'">
<Target Name="_WasmAotCompileApp" Condition="'$(_WasmShouldAOT)' == 'true'">
<PropertyGroup>
<!-- FIXME: do it once -->
<_MonoAotCrossCompilerPath>@(MonoAotCrossCompiler->WithMetadataValue('RuntimeIdentifier','browser-wasm'))</_MonoAotCrossCompilerPath>
......@@ -433,13 +458,11 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
<MonoAOTCompilerDefaultAotArguments Include="deterministic" />
</ItemGroup>
<ItemGroup>
<_AotInputAssemblies Include="@(_WasmAssembliesInternal)" Condition="'%(_WasmAssembliesInternal._InternalForceInterpret)' != 'true'">
<_AotInputAssemblies Include="@(_WasmAssembliesInternal)">
<AotArguments>@(MonoAOTCompilerDefaultAotArguments, ';')</AotArguments>
<ProcessArguments>@(MonoAOTCompilerDefaultProcessArguments, ';')</ProcessArguments>
</_AotInputAssemblies>
<_AOT_InternalForceInterpretAssemblies Include="@(_WasmAssembliesInternal->WithMetadataValue('_InternalForceInterpret', 'true'))" />
<_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)" />
<_WasmAOTSearchPaths Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)" />
......@@ -452,16 +475,13 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
<_AOTCompilerCacheFile>$(_WasmIntermediateOutputPath)aot_compiler_cache.json</_AOTCompilerCacheFile>
</PropertyGroup>
<Error Condition="'$(AOTMode)' == 'llvmonly' and @(_AOT_InternalForceInterpretAssemblies->Count()) > 0"
Text="Builing in AOTMode=LLVMonly, but found some assemblies marked as _InternalForceInterpret: @(_AOT_InternalForceInterpretAssemblies)" />
<Message Text="AOT'ing @(_AotInputAssemblies->Count()) assemblies" Importance="High" />
<!-- Dedup -->
<PropertyGroup Condition="'$(WasmDedup)' == 'true'">
<_WasmDedupAssembly>$(_WasmIntermediateOutputPath)\aot-instances.dll</_WasmDedupAssembly>
</PropertyGroup>
<WriteLinesToFile Condition="'$(WasmDedup)' == 'true'" File="$(_WasmIntermediateOutputPath)/aot-instances.cs" Overwrite="true" Lines="" />
<WriteLinesToFile Condition="'$(WasmDedup)' == 'true'" File="$(_WasmIntermediateOutputPath)/aot-instances.cs" Overwrite="true" Lines="" WriteOnlyWhenDifferent="true" />
<Csc
Condition="'$(WasmDedup)' == 'true'"
Sources="$(_WasmIntermediateOutputPath)\aot-instances.cs"
......@@ -494,34 +514,30 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
DedupAssembly="$(_WasmDedupAssembly)"
CacheFilePath="$(_AOTCompilerCacheFile)"
LLVMDebug="dwarfdebug"
LLVMPath="$(EmscriptenUpstreamBinPath)" >
LLVMPath="$(EmscriptenUpstreamBinPath)"
IntermediateOutputPath="$(_WasmIntermediateOutputPath)">
<Output TaskParameter="CompiledAssemblies" ItemName="_WasmAssembliesInternal" />
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
</MonoAOTCompiler>
<ItemGroup>
<!-- Add back the interpreter-only assemblies -->
<_WasmAssembliesInternal Include="@(_AOT_InternalForceInterpretAssemblies)" />
<_AOTAssemblies Include="@(_WasmAssembliesInternal)" Condition="'%(_WasmAssembliesInternal._InternalForceInterpret)' != 'true'" />
<_BitcodeFile Include="%(_WasmAssembliesInternal.LlvmBitcodeFile)" />
<_BitcodeFile ObjectFile="$(_WasmIntermediateOutputPath)%(FileName).o" />
</ItemGroup>
</Target>
<!-- '$(ArchiveTests)' != 'true' is to skip on CI for now -->
<Target Name="_WasmStripAOTAssemblies" Condition="'$(RunAOTCompilation)' == 'true' and '$(WasmStripAOTAssemblies)' == 'true' and '$(AOTMode)' != 'LLVMOnlyInterp' and '$(ArchiveTests)' != 'true'">
<Target Name="_WasmStripAOTAssemblies" Condition="'$(_WasmShouldAOT)' == 'true' and '$(WasmStripAOTAssemblies)' == 'true' and '$(AOTMode)' != 'LLVMOnlyInterp' and '$(ArchiveTests)' != 'true'">
<PropertyGroup>
<_WasmStrippedAssembliesPath>$([MSBuild]::NormalizeDirectory($(_WasmIntermediateOutputPath), 'stripped-assemblies'))</_WasmStrippedAssembliesPath>
</PropertyGroup>
<ItemGroup>
<_AOTedAssemblies Include="@(_WasmAssembliesInternal)" />
<_WasmStrippedAssemblies
Condition="'%(_WasmAssembliesInternal._InternalForceInterpret)' != 'true'"
Include="@(_WasmAssembliesInternal->'$(_WasmStrippedAssembliesPath)%(FileName)%(Extension)')"
Include="@(_AOTedAssemblies)"
OriginalPath="%(_WasmAssembliesInternal.Identity)" />
<_WasmInterpOnlyAssembly Include="@(_WasmAssembliesInternal->WithMetadataValue('_InternalForceInterpret', 'true'))" />
</ItemGroup>
<!-- Run mono-cil-strip on the assemblies -->
......@@ -531,7 +547,7 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
<ItemGroup>
<_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)" />
<_WasmAssembliesInternal Include="@(_WasmStrippedAssemblies);@(_WasmInterpOnlyAssembly)" />
<_WasmAssembliesInternal Include="@(_WasmStrippedAssemblies)" />
</ItemGroup>
</Target>
......
......@@ -5,8 +5,7 @@
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
<UseMonoRuntime>true</UseMonoRuntime>
<WasmBuildAppAfterThisTarget Condition="'$(WasmBuildAppAfterThisTarget)' == ''">Publish</WasmBuildAppAfterThisTarget>
<WasmBuildAppDependsOn>
<_WasmBuildCoreDependsOn>
_InitializeCommonProperties;
_BeforeWasmBuildApp;
_WasmResolveReferences;
......@@ -15,6 +14,16 @@
_WasmBuildNativeCore;
_WasmGenerateAppBundle;
_AfterWasmBuildApp
</_WasmBuildCoreDependsOn>
<WasmBuildAppDependsOn>
_PrepareForAfterBuild;
$(_WasmBuildCoreDependsOn)
</WasmBuildAppDependsOn>
<WasmNestedPublishAppDependsOn>
_PrepareForNestedPublish;
$(_WasmBuildCoreDependsOn)
</WasmNestedPublishAppDependsOn>
</PropertyGroup>
</Project>
......@@ -5,9 +5,6 @@
<!--
Required public items/properties:
- $(WasmMainJSPath)
- @(WasmAssembliesToBundle) - list of assemblies to package as the wasm app
- %(_InternalForceInterpret) metadata - if true, then skips this assembly from the AOT step.
Error for this to be set with AOTMode=LLVMOnly
- $(EMSDK_PATH) - points to the emscripten sdk location.
......@@ -45,9 +42,6 @@
- $(WasmStripAOTAssemblies) - Whether to run `mono-cil-strip` on the assemblies.
Always set to false!
- $(WasmBuildAppAfterThisTarget) - This target is used as `AfterTargets` for `WasmBuildApp. this
is what triggers the wasm app building. Defaults to `Publish`.
- $(EmccVerbose) - Set to false to disable verbose emcc output.
- $(EmccLinkOptimizationFlag) - Optimization flag to use for the link step
......@@ -58,6 +52,15 @@
- $(EmccExtraCFlags) - Extra emcc flags for compiling native files
- $(EmccTotalMemory) - Total memory specified with `emcc`. Default value: 536870912
- $(WasmBuildAppAfterThisTarget) - This target is used as `AfterTargets` for `WasmBuildApp. this
is what triggers the wasm app building. Defaults to `Build`.
- $(WasmTriggerPublishAppAfterThisTarget) - This target is used as `AfterTargets` for `WasmTriggerPublishApp.
Defaults to `Publish`.
- $(EnableDefaultWasmAssembliesToBundle) - Get list of assemblies to bundle automatically. Defaults to true.
- $(WasmBuildOnlyAfterPublish) - Causes relinking to be done only for Publish. Defaults to false.
- $(RunAOTCompilationAfterBuild) - Run AOT compilation even after Build. By default, it is run only for publish.
Defaults to false.
Public items:
- @(WasmExtraFilesToDeploy) - Files to copy to $(WasmAppDir).
......@@ -83,14 +86,76 @@
<WasmStripAOTAssemblies>false</WasmStripAOTAssemblies>
<_BeforeWasmBuildAppDependsOn />
<IsWasmProject Condition="'$(IsWasmProject)' == '' and '$(OutputType)' != 'Library'">true</IsWasmProject>
<WasmBuildAppAfterThisTarget Condition="'$(WasmBuildAppAfterThisTarget)' == '' and '$(DisableAutoWasmBuildApp)' != 'true'">Build</WasmBuildAppAfterThisTarget>
<WasmTriggerPublishAppAfterThisTarget Condition="'$(DisableAutoWasmPublishApp)' != 'true' and '$(WasmBuildingForNestedPublish)' != 'true'">Publish</WasmTriggerPublishAppAfterThisTarget>
<_WasmNestedPublishAppPreTarget Condition="'$(DisableAutoWasmPublishApp)' != 'true'">Publish</_WasmNestedPublishAppPreTarget>
<EnableDefaultWasmAssembliesToBundle Condition="'$(EnableDefaultWasmAssembliesToBundle)' == ''">true</EnableDefaultWasmAssembliesToBundle>
<WasmBuildOnlyAfterPublish Condition="'$(WasmBuildOnlyAfterPublish)' == '' and '$(DeployOnBuild)' == 'true'">true</WasmBuildOnlyAfterPublish>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)WasmApp.Native.targets" />
<!-- PUBLISH -->
<Target Name="WasmTriggerPublishApp"
AfterTargets="$(WasmTriggerPublishAppAfterThisTarget)"
Condition="'$(IsWasmProject)' == 'true' and '$(WasmBuildingForNestedPublish)' != 'true' and '$(IsCrossTargetingBuild)' != 'true'">
<!-- Use a unique property, so the already run wasm targets can also run -->
<MSBuild Projects="$(MSBuildProjectFile)"
Targets="WasmNestedPublishApp"
Properties="_WasmInNestedPublish_UniqueProperty_XYZ=true;;WasmBuildingForNestedPublish=true;DeployOnBuild=">
<Output TaskParameter="TargetOutputs" ItemName="WasmNestedPublishAppResultItems" />
</MSBuild>
<ItemGroup>
<WasmAssembliesFinal Remove="@(WasmAssembliesFinal)" />
<WasmAssembliesFinal Include="@(WasmNestedPublishAppResultItems)" Condition="'%(WasmNestedPublishAppResultItems.OriginalItemName)' == 'WasmAssembliesFinal'" />
<WasmNativeAsset Remove="@(WasmNativeAsset)" />
<WasmNativeAsset Include="@(WasmNestedPublishAppResultItems)" Condition="'%(WasmNestedPublishAppResultItems.OriginalItemName)' == 'WasmNativeAsset'" />
<FileWrites Include="@(WasmNestedPublishAppResultItems)" Condition="'%(WasmNestedPublishAppResultItems.OriginalItemName)' == 'FileWrites'" />
</ItemGroup>
</Target>
<!-- Public target. Do not depend on this target, as it is meant to be run by a msbuild task -->
<Target Name="WasmNestedPublishApp"
DependsOnTargets="ResolveRuntimePackAssets;$(_WasmNestedPublishAppPreTarget);$(WasmNestedPublishAppDependsOn)"
Condition="'$(WasmBuildingForNestedPublish)' == 'true'"
Returns="@(WasmNativeAsset);@(WasmAssembliesFinal);@(FileWrites)">
<ItemGroup>
<WasmNativeAsset OriginalItemName="WasmNativeAsset" />
<WasmAssembliesFinal OriginalItemName="WasmAssembliesFinal" />
<FileWrites OriginalItemName="FileWrites" />
</ItemGroup>
</Target>
<Target Name="_PrepareForNestedPublish" Condition="'$(WasmBuildingForNestedPublish)' == 'true'">
<PropertyGroup>
<_WasmRuntimeConfigFilePath Condition="$([System.String]::new(%(PublishItemsOutputGroupOutputs.Identity)).EndsWith('$(AssemblyName).runtimeconfig.json'))">@(PublishItemsOutputGroupOutputs)</_WasmRuntimeConfigFilePath>
</PropertyGroup>
<ItemGroup Condition="'$(EnableDefaultWasmAssembliesToBundle)' == 'true' and '$(DisableAutoWasmPublishApp)' != 'true'">
<WasmAssembliesToBundle Remove="@(WasmAssembliesToBundle)" />
<WasmAssembliesToBundle Include="$(PublishDir)\**\*.dll" />
</ItemGroup>
<!-- Having this separate target allows users to cleanly add After/BeforeTargets for this -->
<Target Name="WasmBuildApp" AfterTargets="$(WasmBuildAppAfterThisTarget)" />
<PropertyGroup Condition="'$(_WasmRuntimeConfigFilePath)' == ''">
<_WasmRuntimeConfigFilePath Condition="$([System.String]::new(%(PublishItemsOutputGroupOutputs.Identity)).EndsWith('$(AssemblyName).runtimeconfig.json'))">@(PublishItemsOutputGroupOutputs)</_WasmRuntimeConfigFilePath>
</PropertyGroup>
</Target>
<Import Project="$(MSBuildThisFileDirectory)WasmApp.Native.targets" />
<Target Name="_WasmCoreBuild" BeforeTargets="WasmBuildApp" DependsOnTargets="$(WasmBuildAppDependsOn)" />
<!-- public target for Build -->
<Target Name="WasmBuildApp"
AfterTargets="$(WasmBuildAppAfterThisTarget)"
DependsOnTargets="$(WasmBuildAppDependsOn)"
Condition="'$(IsWasmProject)' == 'true' and '$(WasmBuildingForNestedPublish)' == '' and '$(WasmBuildOnlyAfterPublish)' != 'true' and '$(IsCrossTargetingBuild)' != 'true'" />
<Target Name="_InitializeCommonProperties">
<Error Condition="'$(MicrosoftNetCoreAppRuntimePackDir)' == '' and ('%(ResolvedRuntimePack.PackageDirectory)' == '' or !Exists(%(ResolvedRuntimePack.PackageDirectory)))"
......@@ -106,37 +171,50 @@
<_WasmRuntimePackIncludeDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'include'))</_WasmRuntimePackIncludeDir>
<_WasmRuntimePackSrcDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'src'))</_WasmRuntimePackSrcDir>
<_WasmIntermediateOutputPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm'))</_WasmIntermediateOutputPath>
<_WasmIntermediateOutputPath Condition="'$(WasmBuildingForNestedPublish)' == ''">$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm', 'for-build'))</_WasmIntermediateOutputPath>
<_WasmIntermediateOutputPath Condition="'$(WasmBuildingForNestedPublish)' != ''">$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm', 'for-publish'))</_WasmIntermediateOutputPath>
<_DriverGenCPath>$(_WasmIntermediateOutputPath)driver-gen.c</_DriverGenCPath>
<_WasmShouldAOT Condition="'$(WasmBuildingForNestedPublish)' == 'true' and '$(RunAOTCompilation)' == 'true'">true</_WasmShouldAOT>
<_WasmShouldAOT Condition="'$(RunAOTCompilationAfterBuild)' == 'true' and '$(RunAOTCompilation)' == 'true'">true</_WasmShouldAOT>
<_WasmShouldAOT Condition="'$(_WasmShouldAOT)' == ''">false</_WasmShouldAOT>
</PropertyGroup>
<MakeDir Directories="$(_WasmIntermediateOutputPath)" />
</Target>
<Target Name="_PrepareForAfterBuild" Condition="'$(WasmBuildingForNestedPublish)' != 'true'">
<ItemGroup Condition="'$(EnableDefaultWasmAssembliesToBundle)' == 'true'">
<WasmAssembliesToBundle Include="@(ReferenceCopyLocalPaths);@(MainAssembly)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'" />
</ItemGroup>
</Target>
<Target Name="_BeforeWasmBuildApp" DependsOnTargets="$(_BeforeWasmBuildAppDependsOn)">
<Error Condition="!Exists('$(MicrosoftNetCoreAppRuntimePackRidDir)')" Text="MicrosoftNetCoreAppRuntimePackRidDir=$(MicrosoftNetCoreAppRuntimePackRidDir) doesn't exist" />
<Error Condition="@(WasmAssembliesToBundle->Count()) == 0" Text="WasmAssembliesToBundle item is empty. No assemblies to process" />
<PropertyGroup>
<WasmGenerateAppBundle Condition="'$(WasmGenerateAppBundle)' == ''">true</WasmGenerateAppBundle>
<WasmGenerateAppBundle Condition="'$(WasmGenerateAppBundle)' == '' and '$(OutputType)' != 'Library'">true</WasmGenerateAppBundle>
<WasmGenerateAppBundle Condition="'$(WasmGenerateAppBundle)' == ''">false</WasmGenerateAppBundle>
<WasmAppDir Condition="'$(WasmAppDir)' == ''">$([MSBuild]::NormalizeDirectory($(OutputPath), 'AppBundle'))</WasmAppDir>
<WasmMainAssemblyFileName Condition="'$(WasmMainAssemblyFileName)' == ''">$(TargetFileName)</WasmMainAssemblyFileName>
<WasmAppDir>$([MSBuild]::NormalizeDirectory($(WasmAppDir)))</WasmAppDir>
<_MainAssemblyPath Condition="'%(WasmAssembliesToBundle.FileName)' == $(AssemblyName) and '%(WasmAssembliesToBundle.Extension)' == '.dll' and $(WasmGenerateAppBundle) == 'true'">%(WasmAssembliesToBundle.Identity)</_MainAssemblyPath>
<_WasmRuntimeConfigFilePath Condition="$(_MainAssemblyPath) != ''">$([System.IO.Path]::ChangeExtension($(_MainAssemblyPath), '.runtimeconfig.json'))</_WasmRuntimeConfigFilePath>
<_ParsedRuntimeConfigFilePath Condition="'$(_MainAssemblyPath)' != ''">$([System.IO.Path]::GetDirectoryName($(_MainAssemblyPath)))\runtimeconfig.bin</_ParsedRuntimeConfigFilePath>
<_WasmRuntimeConfigFilePath Condition="'$(_WasmRuntimeConfigFilePath)' == '' and $(_MainAssemblyPath) != ''">$([System.IO.Path]::ChangeExtension($(_MainAssemblyPath), '.runtimeconfig.json'))</_WasmRuntimeConfigFilePath>
<_ParsedRuntimeConfigFilePath Condition="'$(_WasmRuntimeConfigFilePath)' != ''">$([System.IO.Path]::GetDirectoryName($(_WasmRuntimeConfigFilePath)))\runtimeconfig.bin</_ParsedRuntimeConfigFilePath>
</PropertyGroup>
<Warning Condition="'$(WasmGenerateAppBundle)' == 'true' and $(_MainAssemblyPath) == ''" Text="Could not find %24(AssemblyName)=$(AssemblyName) in the assemblies to be bundled.." />
<Warning Condition="'$(WasmGenerateAppBundle)' == 'true' and $(_MainAssemblyPath) == ''" Text="Could not find %24(AssemblyName)=$(AssemblyName).dll in the assemblies to be bundled." />
<Warning Condition="'$(WasmGenerateAppBundle)' == 'true' and $(_WasmRuntimeConfigFilePath) != '' and !Exists($(_WasmRuntimeConfigFilePath))"
Text="Could not find $(_WasmRuntimeConfigFilePath) for $(_MainAssemblyPath)." />
<ItemGroup>
<_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)" />
<_WasmAssembliesInternal Include="@(WasmAssembliesToBundle->Distinct())" />
<_WasmSatelliteAssemblies Remove="@(_WasmSatelliteAssemblies)" />
<_WasmSatelliteAssemblies Include="@(_WasmAssembliesInternal)" />
<_WasmSatelliteAssemblies Remove="@(_WasmSatelliteAssemblies)" Condition="!$([System.String]::Copy('%(Identity)').EndsWith('.resources.dll'))" />
<!-- FIXME: Only include the ones with valid culture name -->
......@@ -166,9 +244,7 @@
</ItemGroup>
</Target>
<Target Name="_WasmGenerateAppBundle" Condition="'$(WasmGenerateAppBundle)' == 'true'" DependsOnTargets="_WasmGenerateRuntimeConfig">
<Error Condition="'$(WasmMainJSPath)' == ''" Text="%24(WasmMainJSPath) property needs to be set" />
<Target Name="_GetWasmGenerateAppBundleDependencies">
<PropertyGroup>
<WasmIcuDataFileName Condition="'$(InvariantGlobalization)' != 'true'">icudt.dat</WasmIcuDataFileName>
......@@ -184,7 +260,16 @@
<WasmNativeAsset Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(WasmIcuDataFileName)" Condition="'$(InvariantGlobalization)' != 'true'" />
<WasmNativeAsset Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)dotnet.timezones.blat" />
</ItemGroup>
</Target>
<Target Name="_WasmGenerateAppBundle"
Inputs="@(_WasmAssembliesInternal);$(WasmMainJSPath);$(WasmIcuDataFileName);@(WasmNativeAsset)"
Outputs="$(WasmAppDir)\.stamp"
Condition="'$(WasmGenerateAppBundle)' == 'true'"
DependsOnTargets="_WasmGenerateRuntimeConfig;_GetWasmGenerateAppBundleDependencies">
<Error Condition="'$(WasmMainJSPath)' == ''" Text="%24(WasmMainJSPath) property needs to be set" />
<RemoveDir Directories="$(WasmAppDir)" />
<WasmAppBuilder
AppDir="$(WasmAppDir)"
MainJS="$(WasmMainJSPath)"
......@@ -202,6 +287,8 @@
</WasmAppBuilder>
<CallTarget Targets="_GenerateRunV8Script" Condition="'$(WasmGenerateRunV8Script)' == 'true'" />
<WriteLinesToFile File="$(WasmAppDir)\.stamp" Lines="" Overwrite="true" />
</Target>
<Target Name="_GenerateRunV8Script">
......
......@@ -5,6 +5,7 @@
<TestRootDir Condition="'$(HELIX_WORKITEM_ROOT)' == ''">$(MSBuildThisFileDirectory)..\wasm_build\</TestRootDir>
<RunAOTCompilation Condition="'$(RunAOTCompilation)' == ''">true</RunAOTCompilation>
<RunAOTCompilationAfterBuild>true</RunAOTCompilationAfterBuild>
<OriginalPublishDir>$(TestRootDir)..\publish\</OriginalPublishDir>
<ExtraFilesPath>$(OriginalPublishDir)..\extraFiles\</ExtraFilesPath>
<IntermediateOutputPath>$(TestRootDir)\obj\</IntermediateOutputPath>
......
......@@ -4,6 +4,7 @@
<PropertyGroup>
<TargetFramework>$(AspNetCoreAppCurrent)</TargetFramework>
<OutputType>Library</OutputType>
<IsWasmProject>true</IsWasmProject>
<Configuration>Debug</Configuration>
<RuntimeConfiguration Condition="'$(RuntimeConfiguration)'==''">Release</RuntimeConfiguration>
......
......@@ -5,6 +5,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<RunAnalyzers>false</RunAnalyzers>
<WasmBuildAppDependsOn>PrepareForWasmBuildApp;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
<WasmGenerateAppBundle>true</WasmGenerateAppBundle>
</PropertyGroup>
<ItemGroup>
......
......@@ -191,11 +191,15 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
/// </summary>
public string? CacheFilePath { get; set; }
[Required]
public string IntermediateOutputPath { get; set; } = string.Empty;
[Output]
public string[]? FileWrites { get; private set; }
private List<string> _fileWrites = new();
private IList<ITaskItem>? _assembliesToCompile;
private ConcurrentDictionary<string, ITaskItem> compiledAssemblies = new();
private MonoAotMode parsedAotMode;
......@@ -207,7 +211,7 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
private int _numCompiled;
private int _totalNumAssemblies;
public override bool Execute()
private bool ProcessAndValidateArguments()
{
if (!File.Exists(CompilerBinaryPath))
{
......@@ -230,6 +234,9 @@ public 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<ITaskItem> EnsureAndGetAssembliesInTheSameDir(ITaskItem[] originalAssemblies)
{
List<ITaskItem> filteredAssemblies = new();
string firstAsmDir = Path.GetDirectoryName(originalAssemblies[0].GetMetadata("FullPath")) ?? string.Empty;
bool allInSameDir = true;
foreach (var origAsm in originalAssemblies)
{
if (allInSameDir && Path.GetDirectoryName(origAsm.GetMetadata("FullPath")) != firstAsmDir)
allInSameDir = false;
if (ShouldSkip(origAsm))
{
if (parsedAotMode == MonoAotMode.LLVMOnly)
throw new LogAsErrorException($"Building in AOTMode=LLVMonly is not compatible with excluding any assemblies for AOT. Excluded assembly: {origAsm.ItemSpec}");
Log.LogMessage(MessageImportance.Low, $"Skipping {origAsm.ItemSpec} because it has %(AOT_InternalForceToInterpret)=true");
continue;
}
filteredAssemblies.Add(origAsm);
}
if (allInSameDir)
return filteredAssemblies;
// Copy to aot-in
string aotInPath = Path.Combine(IntermediateOutputPath, "aot-in");
Directory.CreateDirectory(aotInPath);
List<ITaskItem> newAssemblies = new();
foreach (var origAsm in originalAssemblies)
{
string asmPath = origAsm.GetMetadata("FullPath");
string newPath = Path.Combine(aotInPath, Path.GetFileName(asmPath));
// FIXME: delete files not in originalAssemblies though
// FIXME: or .. just delete the whole dir?
if (Utils.CopyIfDifferent(asmPath, newPath, useHash: true))
Log.LogMessage(MessageImportance.Low, $"Copying {asmPath} to {newPath}");
if (!ShouldSkip(origAsm))
{
ITaskItem newAsm = new TaskItem(newPath);
origAsm.CopyMetadataTo(newAsm);
newAssemblies.Add(newAsm);
}
}
return newAssemblies;
static bool ShouldSkip(ITaskItem asmItem)
=> bool.TryParse(asmItem.GetMetadata("AOT_InternalForceToInterpret"), out bool skip) && skip;
}
private bool PrecompileLibrary(ITaskItem assemblyItem, string? monoPaths)
{
string assembly = assemblyItem.ItemSpec;
string assembly = assemblyItem.GetMetadata("FullPath");
string assemblyDir = Path.GetDirectoryName(assembly)!;
var aotAssembly = new TaskItem(assembly);
var aotArgs = new List<string>();
......@@ -567,38 +650,20 @@ 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<string> allPaths = new HashSet<string>();
foreach (var aItem in Assemblies)
{
string filename = aItem.ItemSpec;
processArgs.Add(filename);
string dir = Path.GetDirectoryName(filename)!;
if (!allPaths.Contains(dir))
{
allPaths.Add(dir);
if (sb.Length > 0)
sb.Append(Path.PathSeparator);
sb.Append(dir);
}
}
if (sb.Length > 0)
sb.Append(Path.PathSeparator);
sb.Append(monoPaths);
paths = sb.ToString();
foreach (var aItem in _assembliesToCompile!)
processArgs.Add(aItem.ItemSpec);
}
else
{
paths = $"{assemblyDir}{Path.PathSeparator}{monoPaths}";
processArgs.Add('"' + assemblyFilename + '"');
}
monoPaths = $"{assemblyDir}{Path.PathSeparator}{monoPaths}";
var envVariables = new Dictionary<string, string>
{
{"MONO_PATH", paths},
{"MONO_PATH", monoPaths },
{"MONO_ENV_OPTIONS", string.Empty} // we do not want options to be provided out of band to the cross compilers
};
......@@ -713,7 +778,7 @@ private void PrecompileLibraryParallel(ITaskItem assemblyItem, string? monoPaths
state.Break();
}
private bool GenerateAotModulesTable(ITaskItem[] assemblies, string[]? profilers, string outputFile)
private bool GenerateAotModulesTable(IEnumerable<ITaskItem> assemblies, string[]? profilers, string outputFile)
{
var symbols = new List<string>();
foreach (var asm in assemblies)
......@@ -831,12 +896,12 @@ private bool TryGetAssemblyName(string asmPath, [NotNullWhen(true)] out string?
}
}
private IList<ITaskItem> ConvertAssembliesDictToOrderedList(ConcurrentDictionary<string, ITaskItem> dict, ITaskItem[] items)
private static IList<ITaskItem> ConvertAssembliesDictToOrderedList(ConcurrentDictionary<string, ITaskItem> dict, IList<ITaskItem> originalAssemblies)
{
List<ITaskItem> outItems = new(items.Length);
foreach (ITaskItem item in items)
List<ITaskItem> outItems = new(originalAssemblies.Count);
foreach (ITaskItem item in originalAssemblies)
{
if (!dict.TryGetValue(item.ItemSpec, out ITaskItem? dictItem))
if (!dict.TryGetValue(item.GetMetadata("FullPath"), out ITaskItem? dictItem))
throw new LogAsErrorException($"Bug: Could not find item in the dict with key {item.ItemSpec}");
outItems.Add(dictItem);
......@@ -931,23 +996,26 @@ 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);
}
}
}
......
......@@ -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)
......
......@@ -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)
......
......@@ -23,6 +23,9 @@ public class IcallTableGenerator : Task
[Required, NotNull]
public string? OutputPath { get; set; }
[Output]
public string? FileWrites { get; private set; } = "";
private List<Icall> _icalls = new List<Icall> ();
private Dictionary<string, IcallClass> _runtimeIcalls = new Dictionary<string, IcallClass> ();
......@@ -58,6 +61,7 @@ public 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);
}
......
......@@ -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
......
......@@ -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 = @"<html><body><script type=""text/javascript"" src=""runtime.js""></script></body></html>";
File.WriteAllText(Path.Combine(AppDir, "index.html"), html);
string indexHtmlPath = Path.Combine(AppDir, "index.html");
if (!File.Exists(indexHtmlPath))
{
var html = @"<html><body><script type=""text/javascript"" src=""runtime.js""></script></body></html>";
File.WriteAllText(indexHtmlPath, html);
}
foreach (var assembly in _assemblies)
{
......
......@@ -16,6 +16,7 @@
<ItemGroup>
<Compile Include="..\Common\Utils.cs" />
<Compile Include="..\Common\LogAsErrorException.cs" />
<PackageReference Include="Microsoft.Build" Version="$(RefOnlyMicrosoftBuildVersion)" />
<PackageReference Include="Microsoft.Build.Framework" Version="$(RefOnlyMicrosoftBuildFrameworkVersion)" />
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.IO;
using System.Linq;
using Xunit;
using Xunit.Abstractions;
#nullable enable
namespace Wasm.Build.Tests
{
public class BlazorWasmBuildPublishTests : BuildTestBase
{
public BlazorWasmBuildPublishTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
: base(output, buildContext)
{
_enablePerTestCleanup = true;
}
[ConditionalTheory(typeof(BuildTestBase), nameof(IsNotUsingWorkloads))]
[InlineData("Debug")]
[InlineData("Release")]
public void DefaultTemplate_WithoutWorkload(string config)
{
string id = $"blz_no_workload_{config}";
CreateBlazorWasmTemplateProject(id);
// Build
BuildInternal(id, config, publish: false);
AssertBlazorBootJson(config, isPublish: false);
// Publish
BuildInternal(id, config, publish: true);
AssertBlazorBootJson(config, isPublish: true);
}
[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
[InlineData("Debug")]
[InlineData("Release")]
public void DefaultTemplate_NoAOT_WithWorkload(string config)
{
string id = $"blz_no_aot_{config}";
CreateBlazorWasmTemplateProject(id);
BlazorBuild(id, config, NativeFilesType.FromRuntimePack);
if (config == "Release")
{
// relinking in publish for Release config
BlazorPublish(id, config, NativeFilesType.Relinked);
}
else
{
BlazorPublish(id, config, NativeFilesType.FromRuntimePack);
}
}
[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
[InlineData("Debug")]
[InlineData("Release")]
public void DefaultTemplate_AOT_InProjectFile(string config)
{
string id = $"blz_aot_prj_file_{config}";
string projectFile = CreateBlazorWasmTemplateProject(id);
AddItemsPropertiesToProject(projectFile, extraProperties: "<RunAOTCompilation>true</RunAOTCompilation>");
// No relinking, no AOT
BlazorBuild(id, config, NativeFilesType.FromRuntimePack);
// will aot
BlazorPublish(id, config, NativeFilesType.AOT);
// build again
BlazorBuild(id, config, NativeFilesType.FromRuntimePack);
}
[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
[InlineData("Debug", true)]
[InlineData("Debug", false)]
[InlineData("Release", true)]
[InlineData("Release", false)]
public void NativeBuild_WithDeployOnBuild_UsedByVS(string config, bool nativeRelink)
{
string id = $"blz_deploy_on_build_{config}_{nativeRelink}";
string projectFile = CreateProjectWithNativeReference(id);
AddItemsPropertiesToProject(projectFile, extraProperties: nativeRelink ? string.Empty : "<RunAOTCompilation>true</RunAOTCompilation>");
// build with -p:DeployOnBuild=true, and that will trigger a publish
(CommandResult res, _) = BuildInternal(id, config, publish: false, "-p:DeployOnBuild=true");
var expectedFileType = nativeRelink ? NativeFilesType.Relinked : NativeFilesType.AOT;
AssertDotNetNativeFiles(expectedFileType, config, forPublish: true);
AssertBlazorBundle(config, isPublish: true, dotnetWasmFromRuntimePack: false);
if (expectedFileType == NativeFilesType.AOT)
{
// check for this too, so we know the format is correct for the negative
// test for jsinterop.webassembly.dll
Assert.Contains("Microsoft.JSInterop.dll -> Microsoft.JSInterop.dll.bc", res.Output);
// make sure this assembly gets skipped
Assert.DoesNotContain("Microsoft.JSInterop.WebAssembly.dll -> Microsoft.JSInterop.WebAssembly.dll.bc", res.Output);
}
// Check that we linked only for publish
string objBuildDir = Path.Combine(_projectDir!, "obj", config, "net6.0", "wasm", "for-build");
Assert.False(Directory.Exists(objBuildDir), $"Found unexpected {objBuildDir}, which gets creating when relinking during Build");
// double check!
int index = res.Output.IndexOf("pinvoke.c -> pinvoke.o");
Assert.NotEqual(-1, index);
// there should be only one instance of this string!
index = res.Output.IndexOf("pinvoke.c -> pinvoke.o", index + 1);
Assert.Equal(-1, index);
}
// Disabling for now - publish folder can have more than one dotnet*hash*js, and not sure
// how to pick which one to check, for the test
//[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
//[InlineData("Debug")]
//[InlineData("Release")]
//public void DefaultTemplate_AOT_OnlyWithPublishCommandLine_Then_PublishNoAOT(string config)
//{
//string id = $"blz_aot_pub_{config}";
//CreateBlazorWasmTemplateProject(id);
//// No relinking, no AOT
//BlazorBuild(id, config, NativeFilesType.FromRuntimePack);
//// AOT=true only for the publish command line, similar to what
//// would happen when setting it in Publish dialog for VS
//BlazorPublish(id, config, expectedFileType: NativeFilesType.AOT, "-p:RunAOTCompilation=true");
//// publish again, no AOT
//BlazorPublish(id, config, NativeFilesType.Relinked);
//}
[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
[InlineData("Debug")]
[InlineData("Release")]
public void WithNativeReference_AOTInProjectFile(string config)
{
string id = $"blz_nativeref_aot_{config}";
string projectFile = CreateProjectWithNativeReference(id);
AddItemsPropertiesToProject(projectFile, extraProperties: "<RunAOTCompilation>true</RunAOTCompilation>");
BlazorBuild(id, config, expectedFileType: NativeFilesType.Relinked);
BlazorPublish(id, config, expectedFileType: NativeFilesType.AOT);
// will relink
BlazorBuild(id, config, expectedFileType: NativeFilesType.Relinked);
}
[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
[InlineData("Debug")]
[InlineData("Release")]
public void WithNativeReference_AOTOnCommandLine(string config)
{
string id = $"blz_nativeref_aot_{config}";
CreateProjectWithNativeReference(id);
BlazorBuild(id, config, expectedFileType: NativeFilesType.Relinked);
BlazorPublish(id, config, expectedFileType: NativeFilesType.AOT, "-p:RunAOTCompilation=true");
// no aot!
BlazorPublish(id, config, expectedFileType: NativeFilesType.Relinked);
}
private string CreateProjectWithNativeReference(string id)
{
CreateBlazorWasmTemplateProject(id);
string extraItems = @$"
<PackageReference Include=""SkiaSharp"" Version=""2.80.3"" />
<PackageReference Include=""SkiaSharp.NativeAssets.WebAssembly"" Version=""2.80.3"" />
<NativeFileReference Include=""$(SkiaSharpStaticLibraryPath)\2.0.9\*.a"" />
<WasmFilesToIncludeInFileSystem Include=""{Path.Combine(BuildEnvironment.TestAssetsPath, "mono.png")}"" />
";
string projectFile = Path.Combine(_projectDir!, $"{id}.csproj");
AddItemsPropertiesToProject(projectFile, extraItems: extraItems);
return projectFile;
}
}
public enum NativeFilesType { FromRuntimePack, Relinked, AOT };
}
// 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: "<NativeFileReference Include=\"native-lib.o\" />");
res.EnsureSuccessful();
AssertBlazorBundle(config, isPublish: true, dotnetWasmFromRuntimePack: true);
Assert.Contains("but the native references won't be linked in", res.Output);
}
......@@ -71,7 +41,7 @@ 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: "<RunAOTCompilation>true</RunAOTCompilation>",
......@@ -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"), "<Project />");
File.WriteAllText(Path.Combine(_projectDir!, "Directory.Build.targets"), "<Project />");
File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Local.Directory.Build.props"), Path.Combine(_projectDir!, "Directory.Build.props"), overwrite: true);
File.Copy(Path.Combine(BuildEnvironment.TestDataPath, "Blazor.Local.Directory.Build.targets"), Path.Combine(_projectDir!, "Directory.Build.targets"), overwrite: true);
}
AddItemsPropertiesToProject(Path.Combine(_projectDir!, $"{id}.csproj"),
......@@ -136,7 +101,7 @@ private void BuildNet50Project(string config, bool aot, bool expectError, string
InitBlazorWasmProjectDir(id);
string directoryBuildTargets = @"<Project>
<Target Name=""PrintAllProjects"" BeforeTargets=""Build"">
<Target Name=""PrintAllProjects"" BeforeTargets=""Build"">
<Message Text=""** UsingBrowserRuntimeWorkload: '$(UsingBrowserRuntimeWorkload)'"" Importance=""High"" />
</Target>
</Project>";
......@@ -152,11 +117,11 @@ 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
}
}
}
......
......@@ -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();
......
......@@ -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"));
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.IO;
using System.Linq;
using Wasm.Build.NativeRebuild.Tests;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
#nullable enable
namespace Wasm.Build.Tests
{
public class BuildPublishTests : NativeRebuildTestsBase
{
public BuildPublishTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
: base(output, buildContext)
{
}
[Theory]
[BuildAndRun(host: RunHost.V8, aot: false, config: "Release")]
[BuildAndRun(host: RunHost.V8, aot: false, config: "Debug")]
public void BuildThenPublishNoAOT(BuildArgs buildArgs, RunHost host, string id)
{
string projectName = $"build_publish_{buildArgs.Config}";
buildArgs = buildArgs with { ProjectName = projectName };
buildArgs = ExpandBuildArgs(buildArgs);
// no relinking for build
bool relinked = false;
BuildProject(buildArgs,
initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
dotnetWasmFromRuntimePack: !relinked,
id: id,
createProject: true,
publish: false);
Run();
if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product))
throw new XunitException($"Test bug: could not get the build product in the cache");
File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog"));
_testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}");
Console.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}");
// relink by default for Release+publish
relinked = buildArgs.Config == "Release";
BuildProject(buildArgs,
id: id,
dotnetWasmFromRuntimePack: !relinked,
createProject: false,
publish: true,
useCache: false);
Run();
void Run() => RunAndTestWasmApp(
buildArgs, buildDir: _projectDir, expectedExitCode: 42,
test: output => {},
host: host, id: id);
}
[Theory]
[BuildAndRun(host: RunHost.V8, aot: true, config: "Release")]
[BuildAndRun(host: RunHost.V8, aot: true, config: "Debug")]
public void BuildThenPublishWithAOT(BuildArgs buildArgs, RunHost host, string id)
{
string projectName = $"build_publish_{buildArgs.Config}";
buildArgs = buildArgs with { ProjectName = projectName };
buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "<_WasmDevel>true</_WasmDevel>");
// no relinking for build
bool relinked = false;
(_, string output) = BuildProject(buildArgs,
initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
dotnetWasmFromRuntimePack: !relinked,
id: id,
createProject: true,
publish: false,
label: "first_build");
BuildPaths paths = GetBuildPaths(buildArgs);
var pathsDict = GetFilesTable(buildArgs, paths, unchanged: false);
string mainDll = $"{buildArgs.ProjectName}.dll";
var firstBuildStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath));
Assert.False(firstBuildStat["pinvoke.o"].Exists);
Assert.False(firstBuildStat[$"{mainDll}.bc"].Exists);
CheckOutputForNativeBuild(expectAOT: false, expectRelinking: relinked, buildArgs, output);
Run(expectAOT: false);
if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product))
throw new XunitException($"Test bug: could not get the build product in the cache");
File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog"));
_testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}");
Console.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}");
// relink by default for Release+publish
(_, output) = BuildProject(buildArgs,
id: id,
dotnetWasmFromRuntimePack: false,
createProject: false,
publish: true,
useCache: false,
label: "first_publish");
var publishStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath));
Assert.True(publishStat["pinvoke.o"].Exists);
Assert.True(publishStat[$"{mainDll}.bc"].Exists);
CheckOutputForNativeBuild(expectAOT: true, expectRelinking: false, buildArgs, output);
CompareStat(firstBuildStat, publishStat, pathsDict.Values);
Run(expectAOT: true);
// second build
(_, output) = BuildProject(buildArgs,
initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
dotnetWasmFromRuntimePack: !relinked,
id: id,
createProject: true,
publish: false,
label: "second_build");
var secondBuildStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath));
// no relinking, or AOT
CheckOutputForNativeBuild(expectAOT: false, expectRelinking: false, buildArgs, output);
// no native files changed
pathsDict.UpdateTo(unchanged: true);
CompareStat(publishStat, secondBuildStat, pathsDict.Values);
void Run(bool expectAOT) => RunAndTestWasmApp(
buildArgs with { AOT = expectAOT },
buildDir: _projectDir, expectedExitCode: 42,
host: host, id: id);
}
void CheckOutputForNativeBuild(bool expectAOT, bool expectRelinking, BuildArgs buildArgs, string buildOutput)
{
AssertSubstring($"{buildArgs.ProjectName}.dll -> {buildArgs.ProjectName}.dll.bc", buildOutput, expectAOT);
AssertSubstring($"{buildArgs.ProjectName}.dll.bc -> {buildArgs.ProjectName}.dll.o", buildOutput, expectAOT);
AssertSubstring("pinvoke.c -> pinvoke.o", buildOutput, expectRelinking || expectAOT);
}
}
}
......@@ -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<string> matchingDirs = Directory.EnumerateDirectories(parentDir,
dirName,
new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive });
string? first = matchingDirs.FirstOrDefault();
if (matchingDirs.Count() > 1)
throw new Exception($"Found multiple directories with names that differ only in case. {string.Join(", ", matchingDirs.ToArray())}");
return first ?? Path.Combine(parentDir, dirName);
}
protected string GetBinDir(string config, string targetFramework=s_targetFramework, string? baseDir=null)
{
var dir = baseDir ?? _projectDir;
......@@ -615,26 +773,34 @@ 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<string, FileStat> StatFiles(IEnumerable<string> fullpaths)
{
Dictionary<string, FileStat> table = new();
foreach (string file in fullpaths)
{
if (File.Exists(file))
table.Add(Path.GetFileName(file), new FileStat(FullPath: file, Exists: true, LastWriteTimeUtc: File.GetLastWriteTimeUtc(file), Length: new FileInfo(file).Length));
else
table.Add(Path.GetFileName(file), new FileStat(FullPath: file, Exists: false, LastWriteTimeUtc: DateTime.MinValue, Length: 0));
}
return table;
}
protected static string s_mainReturns42 = @"
public class TestClass {
public static int Main()
......@@ -669,4 +858,6 @@ 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);
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.IO;
using System.Linq;
using Wasm.Build.NativeRebuild.Tests;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
#nullable enable
namespace Wasm.Build.Tests;
public class CleanTests : NativeRebuildTestsBase
{
public CleanTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
: base(output, buildContext)
{
}
[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
[InlineData("Debug")]
[InlineData("Release")]
public void Blazor_BuildThenClean_NativeRelinking(string config)
{
string id = Path.GetRandomFileName();
InitBlazorWasmProjectDir(id);
string projectFile = CreateBlazorWasmTemplateProject(id);
string extraProperties = @"<_WasmDevel>true</_WasmDevel>
<WasmBuildNative>true</WasmBuildNative>";
AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties);
BlazorBuild(id, config, NativeFilesType.Relinked);
string relinkDir = Path.Combine(_projectDir!, "obj", config, "net6.0", "wasm", "for-build");
Assert.True(Directory.Exists(relinkDir), $"Could not find expected relink dir: {relinkDir}");
string logPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-clean.binlog");
new DotNetCommand(s_buildEnv)
.WithWorkingDirectory(_projectDir!)
.ExecuteWithCapturedOutput("build", "-t:Clean", $"-p:Configuration={config}", $"-bl:{logPath}")
.EnsureSuccessful();
AssertEmptyOrNonExistantDirectory(relinkDir);
}
[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
[InlineData("Debug")]
[InlineData("Release")]
public void Blazor_BuildNoNative_ThenBuildNative_ThenClean(string config)
=> Blazor_BuildNativeNonNative_ThenCleanTest(config, firstBuildNative: false);
[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
[InlineData("Debug")]
[InlineData("Release")]
public void Blazor_BuildNative_ThenBuildNonNative_ThenClean(string config)
=> Blazor_BuildNativeNonNative_ThenCleanTest(config, firstBuildNative: true);
private void Blazor_BuildNativeNonNative_ThenCleanTest(string config, bool firstBuildNative)
{
string id = Path.GetRandomFileName();
InitBlazorWasmProjectDir(id);
string projectFile = CreateBlazorWasmTemplateProject(id);
string extraProperties = @"<_WasmDevel>true</_WasmDevel>";
AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties);
bool relink = firstBuildNative;
BuildInternal(id, config, publish: false,
extraArgs: relink ? "-p:WasmBuildNative=true" : string.Empty);
string relinkDir = Path.Combine(_projectDir!, "obj", config, "net6.0", "wasm", "for-build");
if (relink)
Assert.True(Directory.Exists(relinkDir), $"Could not find expected relink dir: {relinkDir}");
relink = !firstBuildNative;
BuildInternal(id, config, publish: false,
extraArgs: relink ? "-p:WasmBuildNative=true" : string.Empty);
if (relink)
Assert.True(Directory.Exists(relinkDir), $"Could not find expected relink dir: {relinkDir}");
string logPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-clean.binlog");
new DotNetCommand(s_buildEnv)
.WithWorkingDirectory(_projectDir!)
.ExecuteWithCapturedOutput("build", "-t:Clean", $"-p:Configuration={config}", $"-bl:{logPath}")
.EnsureSuccessful();
AssertEmptyOrNonExistantDirectory(relinkDir);
}
private void AssertEmptyOrNonExistantDirectory(string dirPath)
{
Console.WriteLine($"dirPath: {dirPath}");
if (!Directory.Exists(dirPath))
return;
var files = Directory.GetFileSystemEntries(dirPath);
if (files.Length == 0)
return;
string found = string.Join(',', files.Select(p => Path.GetFileName(p)));
throw new XunitException($"Expected dir {dirPath} to be empty, but found: {found}");
}
}
......@@ -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;
}
}
}
......@@ -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<string, (string fullPath, bool unchanged)> dict, bool unchanged, params string[] filenames)
{
foreach (var filename in filenames)
IEnumerable<string> keys = filenames.Length == 0 ? dict.Keys.ToList() : filenames;
foreach (var filename in keys)
{
if (!dict.TryGetValue(filename, out var oldValue))
{
......
......@@ -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: "<WasmBuildNative>true</WasmBuildNative>");
BuildProject(buildArgs,
initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), projectContents),
initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
dotnetWasmFromRuntimePack: false,
id: id);
......@@ -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 = @"<Target Name=""StopAfterWasmAOT"" AfterTargets=""_WasmAotCompileApp"">
<Error Text=""Stopping after AOT"" Condition=""'$(WasmBuildingForNestedPublish)' == 'true'"" />
</Target>";
string projectName = $"mono_aot_cross_{buildArgs.Config}_{buildArgs.AOT}";
buildArgs = buildArgs with { ProjectName = projectName, ExtraBuildArgs = "-p:PublishTrimmed=false -v:n" };
buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "<WasmBuildNative>true</WasmBuildNative>", insertAtEnd: target);
(_, string output) = BuildProject(
buildArgs,
initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
dotnetWasmFromRuntimePack: false,
id: id,
expectSuccess: false);
Assert.Contains("Stopping after AOT", output);
}
[Theory]
[BuildAndRun(host: RunHost.None, aot: true)]
public void IntermediateBitcodeToObjectFilesAreNotLLVMIR(BuildArgs buildArgs, string id)
{
string printFileTypeTarget = @"
<Target Name=""PrintIntermediateFileType"" AfterTargets=""WasmBuildApp"">
<Target Name=""PrintIntermediateFileType"" AfterTargets=""WasmNestedPublishApp"">
<Exec Command=""wasm-dis $(_WasmIntermediateOutputPath)System.Private.CoreLib.dll.o -o $(_WasmIntermediateOutputPath)wasm-dis-out.txt""
ConsoleToMSBuild=""true""
EnvironmentVariables=""@(EmscriptenEnvVars)""
IgnoreExitCode=""true"">
<Output TaskParameter=""ExitCode"" PropertyName=""ExitCode"" />
</Exec>
<Message Text=""wasm-dis exit code: $(ExitCode)"" Importance=""High"" />
<Message Text=""
** wasm-dis exit code: $(ExitCode)
"" Importance=""High"" />
</Target>
";
string projectName = $"bc_to_o_{buildArgs.Config}";
......@@ -71,9 +93,41 @@ 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 = @"<Target Name=""StopAfterWasmAOT"" AfterTargets=""_WasmAotCompileApp"">
<Error Text=""Stopping after AOT"" Condition=""'$(WasmBuildingForNestedPublish)' == 'true'"" />
</Target>
";
AddItemsPropertiesToProject(Path.Combine(_projectDir!, $"{id}.csproj"),
extraItems: null,
extraProperties: null,
atTheEnd: target);
string publishLogPath = Path.Combine(s_buildEnv.LogRootPath, id, $"{id}.binlog");
CommandResult res = new DotNetCommand(s_buildEnv)
.WithWorkingDirectory(_projectDir!)
.ExecuteWithCapturedOutput("publish",
$"-bl:{publishLogPath}",
"-p:RunAOTCompilation=true",
"-p:PublishTrimmed=false",
$"-p:Configuration={config}");
Assert.True(res.ExitCode != 0, "Expected publish to fail");
Assert.Contains("Stopping after AOT", res.Output);
}
}
}
// 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;
......
......@@ -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
{
......
......@@ -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<string, FileStat> oldStat, IDictionary<str
throw new XunitException($"CompareStat failed:{Environment.NewLine}{msg}");
}
internal IDictionary<string, FileStat> StatFiles(IEnumerable<string> fullpaths)
{
Dictionary<string, FileStat> table = new();
foreach (string file in fullpaths)
{
if (File.Exists(file))
table.Add(Path.GetFileName(file), new FileStat(FullPath: file, Exists: true, LastWriteTimeUtc: File.GetLastWriteTimeUtc(file), Length: new FileInfo(file).Length));
else
table.Add(Path.GetFileName(file), new FileStat(FullPath: file, Exists: false, LastWriteTimeUtc: DateTime.MinValue, Length: 0));
}
return table;
}
internal BuildPaths GetBuildPaths(BuildArgs buildArgs)
{
string objDir = GetObjDir(buildArgs.Config);
string bundleDir = Path.Combine(GetBinDir(baseDir: _projectDir, config: buildArgs.Config), "AppBundle");
string wasmDir = Path.Combine(objDir, "wasm");
return new BuildPaths(wasmDir, objDir, GetBinDir(buildArgs.Config), bundleDir);
}
internal IDictionary<string, (string fullPath, bool unchanged)> GetFilesTable(BuildArgs buildArgs, BuildPaths paths, bool unchanged)
{
List<string> files = new()
......@@ -203,7 +181,4 @@ 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);
}
......@@ -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
{
......
......@@ -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
{
......
......@@ -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
{
......
......@@ -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: $"<WasmBuildNative>{(nativeRelink ? "true" : "false")}</WasmBuildNative>");
extraProperties: nativeRelink ? $"<WasmBuildNative>true</WasmBuildNative>" : string.Empty);
BuildProject(buildArgs,
initProject: () =>
......
......@@ -16,15 +16,20 @@ public class SharedBuildPerTestClassFixture : IDisposable
public Dictionary<BuildArgs, BuildProduct> _buildPaths = new();
public void CacheBuild(BuildArgs buildArgs, BuildProduct product)
=> _buildPaths.Add(buildArgs, product);
{
if (product == null)
throw new ArgumentNullException(nameof(product));
if (buildArgs == null)
throw new ArgumentNullException(nameof(buildArgs));
_buildPaths.Add(buildArgs, product);
}
public void RemoveFromCache(string buildPath, bool keepDir=true)
{
KeyValuePair<BuildArgs, BuildProduct>? foundKvp = _buildPaths.Where(kvp => kvp.Value.ProjectDir == buildPath).SingleOrDefault();
if (foundKvp == null)
throw new Exception($"Could not find build path {buildPath} in cache to remove.");
BuildArgs? foundBuildArgs = _buildPaths.Where(kvp => kvp.Value.ProjectDir == buildPath).Select(kvp => kvp.Key).SingleOrDefault();
if (foundBuildArgs is not null)
_buildPaths.Remove(foundBuildArgs);
_buildPaths.Remove(foundKvp.Value.Key);
if (!keepDir)
RemoveDirectory(buildPath);
}
......
......@@ -7,7 +7,6 @@
<BundleXunitRunner>true</BundleXunitRunner>
<CLRTestKind>BuildAndRun</CLRTestKind>
<TestFramework>xunit</TestFramework>
<WasmGenerateAppBundle>false</WasmGenerateAppBundle>
<EnableDefaultItems>true</EnableDefaultItems>
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
<DefineConstants Condition="'$(ContinuousIntegrationBuild)' != 'true'">TEST_DEBUG_CONFIG_ALSO</DefineConstants>
......@@ -18,7 +17,7 @@
<InstallWorkloadForTesting>true</InstallWorkloadForTesting>
<!-- don't run any wasm build steps -->
<WasmBuildAppAfterThisTarget />
<IsWasmProject>false</IsWasmProject>
</PropertyGroup>
<PropertyGroup>
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.IO;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
#nullable enable
namespace Wasm.Build.Tests
{
public class WasmNativeDefaultsTests : BuildTestBase
{
public WasmNativeDefaultsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
: base(output, buildContext)
{
}
[Theory]
/* relink by default for publish+Release */
[InlineData("Release", "", /*aot*/ false, /*build*/ false, /*publish*/ true)]
/* NO relink by default for publish+Release, even when not trimming */
[InlineData("Release", "<PublishTrimmed>false</PublishTrimmed>", /*aot*/ false, /*build*/ false, /*publish*/ false)]
[InlineData("Debug", "", /*aot*/ false, /*build*/ false, /*publish*/ false)]
/* AOT */
[InlineData("Release", "", /*aot*/ true, /*build*/ false, /*publish*/ true)]
[InlineData("Debug", "", /*aot*/ true, /*build*/ false, /*publish*/ true)]
// FIXME: separate test
// [InlineData("Release", "<RunAOTCompilationAfterBuild>true</RunAOTCompilationAfterBuild>",
// /*aot*/ true, /*build*/ true, /*publish*/ true)]
/* AOT not affected by trimming */
[InlineData("Release", "<PublishTrimmed>false</PublishTrimmed>", /*aot*/ true, /*build*/ false, /*publish*/ true)]
[InlineData("Debug", "<PublishTrimmed>false</PublishTrimmed>", /*aot*/ true, /*build*/ false, /*publish*/ true)]
public void Defaults(string config, string extraProperties, bool aot, bool buildValue, bool publishValue)
{
string output = CheckWasmNativeDefaultValue("native_defaults_publish", config, extraProperties, aot, dotnetWasmFromRuntimePack: !publishValue);
Assert.Contains($"** WasmBuildNative: '{buildValue.ToString().ToLower()}', WasmBuildingForNestedPublish: ''", output);
Assert.Contains($"** WasmBuildNative: '{publishValue.ToString().ToLower()}', WasmBuildingForNestedPublish: 'true'", output);
Assert.Contains("Stopping the build", output);
}
[Theory]
/* always relink */
[InlineData("Release", "", /*build*/ true, /*publish*/ true)]
[InlineData("Debug", "", /*build*/ true, /*publish*/ true)]
[InlineData("Release", "<PublishTrimmed>false</PublishTrimmed>", /*build*/ true, /*publish*/ true)]
public void WithNativeReference(string config, string extraProperties, bool buildValue, bool publishValue)
{
string nativeLibPath = Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "native-lib.o");
string nativeRefItem = @$"<NativeFileReference Include=""{nativeLibPath}"" />";
string output = CheckWasmNativeDefaultValue("native_defaults_publish",
config,
extraProperties,
aot: false,
dotnetWasmFromRuntimePack: !publishValue,
extraItems: nativeRefItem);
Assert.Contains($"** WasmBuildNative: '{buildValue.ToString().ToLower()}', WasmBuildingForNestedPublish: ''", output);
Assert.Contains($"** WasmBuildNative: '{publishValue.ToString().ToLower()}', WasmBuildingForNestedPublish: 'true'", output);
Assert.Contains("Stopping the build", output);
}
private string CheckWasmNativeDefaultValue(string projectName,
string config,
string extraProperties,
bool aot,
bool dotnetWasmFromRuntimePack,
string extraItems = "")
{
// builds with -O0
extraProperties += "<_WasmDevel>true</_WasmDevel>";
string printValueTarget = @"
<Target Name=""PrintWasmBuildNative"" AfterTargets=""_SetWasmBuildNativeDefaults"">
<Message Text=""** WasmBuildNative: '$(WasmBuildNative)', WasmBuildingForNestedPublish: '$(WasmBuildingForNestedPublish)'"" Importance=""High"" />
<Error Text=""Stopping the build"" Condition=""$(WasmBuildingForNestedPublish) == 'true'"" />
</Target>";
BuildArgs buildArgs = new(ProjectName: projectName, Config: config, AOT: aot, string.Empty, null);
buildArgs = ExpandBuildArgs(buildArgs,
extraProperties: extraProperties,
extraItems: extraItems,
insertAtEnd: printValueTarget);
(_, string output) = BuildProject(buildArgs,
initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
dotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack,
id: Path.GetRandomFileName(),
expectSuccess: false,
useCache: false);
return output;
}
}
}
......@@ -10,7 +10,8 @@
<!-- SDK tries to download runtime packs when RuntimeIdentifier is set, remove them from PackageDownload item. -->
<Target Name="RemoveRuntimePackFromDownloadItem"
AfterTargets="ProcessFrameworkReferences">
AfterTargets="ProcessFrameworkReferences"
Condition="'$(WasmNativeWorkload)' == 'true'">
<ItemGroup>
<PackageDownload Remove="@(PackageDownload)"
Condition="'$(UsePackageDownload)' == 'true' and $([System.String]::Copy('%(Identity)').StartsWith('Microsoft.NETCore.App.Runtime'))" />
......@@ -21,7 +22,8 @@
<!-- Use local targeting pack for NetCoreAppCurrent. -->
<Target Name="UpdateTargetingAndRuntimePack"
AfterTargets="ResolveFrameworkReferences">
AfterTargets="ResolveFrameworkReferences"
Condition="'$(WasmNativeWorkload)' == 'true'">
<ItemGroup>
<ResolvedTargetingPack Path="$(_MicrosoftNetCoreAppRefDir.TrimEnd('/\'))"
NuGetPackageVersion="$(RuntimePackInWorkloadVersion)"
......@@ -47,7 +49,8 @@
<!-- Update the local targeting pack's version as it's written into the runtimeconfig.json file to select the right framework. -->
<Target Name="UpdateRuntimeFrameworkVersion"
AfterTargets="ResolveTargetingPackAssets">
AfterTargets="ResolveTargetingPackAssets"
Condition="'$(WasmNativeWorkload)' == 'true'">
<ItemGroup>
<RuntimeFramework Version="$(RuntimePackInWorkloadVersion)"
Condition="'%(RuntimeFramework.FrameworkName)' == 'Microsoft.NETCore.App'" />
......@@ -56,7 +59,7 @@
<!-- Filter out conflicting implicit assembly references. -->
<Target Name="FilterImplicitAssemblyReferences"
Condition="'$(DisableImplicitAssemblyReferences)' != 'true'"
Condition="'$(DisableImplicitAssemblyReferences)' != 'true' and '$(WasmNativeWorkload)' == 'true'"
DependsOnTargets="ResolveProjectReferences"
AfterTargets="ResolveTargetingPackAssets">
<ItemGroup>
......
<Project>
<PropertyGroup Condition="'$(RuntimeSrcDir)' != '' and '$(WasmBuildSupportDir)' == ''">
<ArtifactsBinDir>$(RuntimeSrcDir)\artifacts\bin\</ArtifactsBinDir>
<MicrosoftNetCoreAppRuntimePackLocationToUse>$([MSBuild]::NormalizeDirectory($(ArtifactsBinDir), 'microsoft.netcore.app.runtime.browser-wasm', $(RuntimeConfig)))</MicrosoftNetCoreAppRuntimePackLocationToUse>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeSrcDir)' == '' and '$(WasmBuildSupportDir)' != ''">
<BuildBaseDir>$(WasmBuildSupportDir)\</BuildBaseDir>
<MicrosoftNetCoreAppRuntimePackLocationToUse>$([MSBuild]::NormalizeDirectory($(BuildBaseDir), 'microsoft.netcore.app.runtime.browser-wasm'))</MicrosoftNetCoreAppRuntimePackLocationToUse>
</PropertyGroup>
</Project>
<Project>
<PropertyGroup>
<_MicrosoftNetCoreAppRefDir>$(AppRefDir)\</_MicrosoftNetCoreAppRefDir>
</PropertyGroup>
<Target Name="PrintRuntimePackPath" BeforeTargets="Publish">
<Message Text="** MicrosoftNetCoreAppRuntimePackDir : %(ResolvedRuntimePack.PackageDirectory)" Importance="High" />
</Target>
<!-- Use local targeting pack for NetCoreAppCurrent. -->
<Target Name="UpdateTargetingAndRuntimePack"
AfterTargets="ResolveFrameworkReferences">
<ItemGroup>
<ResolvedTargetingPack Path="$(_MicrosoftNetCoreAppRefDir.TrimEnd('/\'))"
NuGetPackageVersion="$(RuntimePackInWorkloadVersion)"
PackageDirectory="$(_MicrosoftNetCoreAppRefDir.TrimEnd('/\'))"
Condition="'%(ResolvedTargetingPack.RuntimeFrameworkName)' == 'Microsoft.NETCore.App' and
Exists('$(_MicrosoftNetCoreAppRefDir)data\FrameworkList.xml')" />
<ResolvedRuntimePack
Update="Microsoft.NETCore.App.Runtime.Mono.browser-wasm"
FrameworkName="Microsoft.NETCore.App"
NuGetPackageId="Microsoft.NETCore.App.Runtime.Mono.browser-wasm"
NuGetPackageVersion="$(RuntimePackInWorkloadVersion)"
PackageDirectory="$(MicrosoftNetCoreAppRuntimePackLocationToUse)"
RuntimeIdentifier="browser-wasm" />
<ResolvedFrameworkReference Update="Microsoft.NETCore.App"
TargetingPackPath="$(_MicrosoftNetCoreAppRefDir.TrimEnd('/\'))"
RuntimePackName="Microsoft.NETCore.App.Runtime.Mono.browser-wasm"
RuntimePackVersion="$(RuntimePackInWorkloadVersion)"
RuntimePackPath="$(MicrosoftNetCoreAppRuntimePackLocationToUse)"
RuntimeIdentifier="browser-wasm" />
</ItemGroup>
</Target>
</Project>
......@@ -7,8 +7,4 @@
</PropertyGroup>
<Import Project="$(_WasmTargetsDir)WasmApp.LocalBuild.props" Condition="Exists('$(_WasmTargetsDir)WasmApp.LocalBuild.props')" />
<PropertyGroup>
<WasmBuildAppDependsOn>PrepareForWasmBuild;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
</PropertyGroup>
</Project>
......@@ -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" />
</Target>
<Target Name="PrepareForWasmBuild">
<ItemGroup>
<WasmAssembliesToBundle Include="$(TargetDir)publish\**\*.dll" />
</ItemGroup>
</Target>
<Target Name="PrintRuntimePackPath" BeforeTargets="Build">
<Message Text="** MicrosoftNetCoreAppRuntimePackDir : %(ResolvedRuntimePack.PackageDirectory)" Importance="High" />
</Target>
......
<Project>
<PropertyGroup>
<WasmBuildAppDependsOn>PrepareForWasmBuild;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
<_MicrosoftNetCoreAppRefDir>$(AppRefDir)\</_MicrosoftNetCoreAppRefDir>
</PropertyGroup>
<Target Name="PrepareForWasmBuild">
<ItemGroup>
<WasmAssembliesToBundle Include="$(TargetDir)publish\**\*.dll" />
</ItemGroup>
</Target>
<Target Name="PrintRuntimePackPath" BeforeTargets="Publish">
<Message Text="** MicrosoftNetCoreAppRuntimePackDir : %(ResolvedRuntimePack.PackageDirectory)" Importance="High" />
</Target>
......
......@@ -10,14 +10,17 @@
<BuildDir>$(MSBuildThisFileDirectory)\obj\$(Configuration)\wasm</BuildDir>
<AppDir>$(TestBinDir)/WasmApp/</AppDir>
<NETCoreAppMaximumVersion>99.0</NETCoreAppMaximumVersion>
<IsWasmProject>true</IsWasmProject>
<WasmGenerateAppBundle>true</WasmGenerateAppBundle>
<WasmAppBuilderTasksAssemblyPath>$(CORE_ROOT)\WasmAppBuilder\WasmAppBuilder.dll</WasmAppBuilderTasksAssemblyPath>
<MonoAOTCompilerTasksAssemblyPath>$(CORE_ROOT)\MonoAOTCompiler\MonoAOTCompiler.dll</MonoAOTCompilerTasksAssemblyPath>
<JsonToItemsTaskFactoryTasksAssemblyPath>$(CORE_ROOT)\JsonToItemsTaskFactory\JsonToItemsTaskFactory.dll</JsonToItemsTaskFactoryTasksAssemblyPath>
<RuntimeConfigParserTasksAssemblyPath>$(CORE_ROOT)\RuntimeConfigParser\RuntimeConfigParser.dll</RuntimeConfigParserTasksAssemblyPath>
<WasmBuildAppDependsOn>BuildApp;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
</PropertyGroup>
<Target Name="BuildApp" BeforeTargets="WasmBuildApp">
<Target Name="BuildApp">
<PropertyGroup>
<WasmMainAssemblyFileName>$(TestAssemblyFileName)</WasmMainAssemblyFileName>
<WasmAppDir>$(AppDir)</WasmAppDir>
......
......@@ -16,13 +16,10 @@
<Content Include="index.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<WasmExtraFilesToDeploy Include="index.html" />
<ProjectReference Include="ApplyUpdateReferencedAssembly\ApplyUpdateReferencedAssembly.csproj" />
</ItemGroup>
<Target Name="AfterWasmBuildApp" AfterTargets="WasmBuildApp">
<Copy SourceFiles="$(OutDir)\index.html" DestinationFolder="$(WasmAppDir)" />
</Target>
<Target Name="PreserveEnCAssembliesFromLinking"
Condition="'$(TargetOS)' == 'Browser' and '$(EnableAggressiveTrimming)' == 'true'"
BeforeTargets="ConfigureTrimming">
......
......@@ -5,13 +5,9 @@
<ExpectedExitCode>42</ExpectedExitCode>
<WasmMainJSPath>runtime.js</WasmMainJSPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<WasmExtraFilesToDeploy Include="index.html" />
</ItemGroup>
<Target Name="AfterWasmBuildApp" AfterTargets="WasmBuildApp">
<Copy SourceFiles="$(MSBuildThisFileDirectory)\index.html" DestinationFolder="$(WasmAppDir)" />
</Target>
</Project>
......@@ -2,13 +2,6 @@
<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.targets, '$(MSBuildThisFileDirectory)..'))" />
<PropertyGroup>
<WasmBuildAppDependsOn>PrepareForWasmBuild;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
<WasmAppDir>$(OutputPath)\$(Configuration)\AppBundle\</WasmAppDir>
</PropertyGroup>
<Target Name="PrepareForWasmBuild">
<ItemGroup>
<WasmAssembliesToBundle Include="$(TargetDir)publish\*.dll" />
</ItemGroup>
</Target>
</Project>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册