dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.39k stars 10k forks source link

Replacing dotnet.wasm #38401

Closed elringus closed 2 years ago

elringus commented 2 years ago

Is it somehow possible to replace dotnet.wasm module which is referenced via WasmNativeAsset and WasmAotAssets?

I've built my own module from sources and need to use it instead of the one bundled with Sdk.BlazorWebAssembly.

I've tried adding the following in my csproj:

<ItemGroup>
    <WasmNativeAsset Include="dotnet.wasm"/>
</ItemGroup>

— but the build fails with:

Microsoft (R) Build Engine version 17.0.0+c9eb9dd64 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
  Test -> C:\Users\Elringus\Documents\GitHub\DotNetJS\Runtime\test\project\bin\Release\net6.0\Test.dll
  Test (Blazor output) -> C:\Users\Elringus\Documents\GitHub\DotNetJS\Runtime\test\project\bin\Release\net6.0\wwwroot
  Optimizing assemblies for size, which may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
  Optimizing assemblies for size, which may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
  Compiling native assets with emcc. This may take a while ...
  [1/3] skipped unchanged files
  [2/3] pinvoke.c -> pinvoke.o [took 0.48s]
  [3/3] corebindings.c -> corebindings.o [took 0.52s]
C:\Program Files\dotnet\sdk\6.0.100\Sdks\Microsoft.NET.Sdk.BlazorWebAssembly\targets\Microsoft.NET.Sdk.BlazorWebAssembly.6_0.targets(412,5): error : System.InvalidOperationException: Sequence contains more than one matching element [C:\Users\Elringus\Documents\GitHub\DotNetJS\Runtime\test\project\Test.csproj]
C:\Program Files\dotnet\sdk\6.0.100\Sdks\Microsoft.NET.Sdk.BlazorWebAssembly\targets\Microsoft.NET.Sdk.BlazorWebAssembly.6_0.targets(412,5): error :    at System.Linq.ThrowHelper.ThrowMoreThanOneMatchException() [C:\Users\Elringus\Documents\GitHub\DotNetJS\Runtime\test\project\Test.csproj]
C:\Program Files\dotnet\sdk\6.0.100\Sdks\Microsoft.NET.Sdk.BlazorWebAssembly\targets\Microsoft.NET.Sdk.BlazorWebAssembly.6_0.targets(412,5): error :    at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Func`2 predicate, Boolean& found) [C:\Users\Elringus\Documents\GitHub\DotNetJS\Runtime\test\project\Test.csproj]
C:\Program Files\dotnet\sdk\6.0.100\Sdks\Microsoft.NET.Sdk.BlazorWebAssembly\targets\Microsoft.NET.Sdk.BlazorWebAssembly.6_0.targets(412,5): error :    at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source, Func`2 predicate) [C:\Users\Elringus\Documents\GitHub\DotNetJS\Runtime\test\project\Test.csproj]
C:\Program Files\dotnet\sdk\6.0.100\Sdks\Microsoft.NET.Sdk.BlazorWebAssembly\targets\Microsoft.NET.Sdk.BlazorWebAssembly.6_0.targets(412,5): error :    at Microsoft.NET.Sdk.BlazorWebAssembly.ComputeBlazorPublishAssets.ProcessNativeAssets(Dictionary`2 nativeAssets, IDictionary`2 resolvedPublishFilesToRemove, Dictionary`2 resolvedNativeAssetToPublish, Dictionary`2 compressedRepresentations, List`1 filesToRemove) [C:\Users\Elringus\Documents\GitHub\DotNetJS\Runtime\test\project\Test.csproj]
C:\Program Files\dotnet\sdk\6.0.100\Sdks\Microsoft.NET.Sdk.BlazorWebAssembly\targets\Microsoft.NET.Sdk.BlazorWebAssembly.6_0.targets(412,5): error :    at Microsoft.NET.Sdk.BlazorWebAssembly.ComputeBlazorPublishAssets.Execute() [C:\Users\Elringus\Documents\GitHub\DotNetJS\Runtime\test\project\Test.csproj]

I guess it fails to figure which module to use. I've tried removing the built-in one before adding my own:

<ItemGroup>
    <WasmNativeAsset Remove="**/*.wasm"/>
    <WasmNativeAsset Include="dotnet.wasm"/>
</ItemGroup>

— but it didn't help.

dotnet-issue-labeler[bot] commented 2 years ago

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

KalleOlaviNiemitalo commented 2 years ago

I'd try doing the Remove either in Directory.Build.targets (if the WasmNativeAsset item is normally added by a .targets file in a NuGet package) or in a custom target having AfterTargets= "LoadStaticWebAssetsBuildManifest" (if that's what normally adds the item).

elringus commented 2 years ago

I'd try doing the Remove either in Directory.Build.targets (if the WasmNativeAsset item is normally added by a .targets file in a NuGet package) or in a custom target having AfterTargets= "LoadStaticWebAssetsBuildManifest" (if that's what normally adds the item).

Thanks for the advice! I've tried both:

<Target Name="RemoveBuiltinWasm" AfterTargets="LoadStaticWebAssetsBuildManifest">
    <ItemGroup>
        <WasmNativeAsset Remove="**/*.wasm"/>
    </ItemGroup>
</Target>

and the same after PrepareForILLink, as it should be there before the linking, but unfortunately it didn't help.

I've tried searching this repo hoping to find where it's actually added, but it looks like it's added somewhere else: https://github.com/dotnet/sdk/search?q=WasmNativeAsset

elringus commented 2 years ago

I've probably found where it's added: https://github.com/dotnet/runtime/blob/release/6.0/src/mono/wasm/build/WasmApp.targets#L257

But this is still producing the same error:

<Target Name="RemoveBuiltinModule" AfterTargets="_WasmBuildNative">
    <ItemGroup>
        <WasmNativeAsset Remove="**/*.wasm"/>
    </ItemGroup>
</Target>
KalleOlaviNiemitalo commented 2 years ago

Oh, this

        <WasmNativeAsset Remove="**/*.wasm"/>

I believe it searches for wasm files within your project directory and removes the corresponding WasmNativeAsset items. But if the items correspond to files that are outside your source tree, then they won't match and won't be removed. So you should instead use

<WasmNativeAsset Remove="@(WasmNativeAsset)" Condition="%(Extension) == '.wasm'"/>

which removes the existing items that have the .wasm extension, regardless of what files exist.

KalleOlaviNiemitalo commented 2 years ago

You can also add %(Filename) to the condition, to remove only dotnet.wasm while ignoring the drive and directory part of the path.

elringus commented 2 years ago

Thank you for the help! It no longer produce the error with the following:

<Target Name="RemoveBuiltinModule" AfterTargets="PrepareForILLink">
    <ItemGroup>
        <WasmNativeAsset Remove="@(WasmNativeAsset)" Condition="%(Extension) == '.wasm'"/>
    </ItemGroup>
</Target>

<ItemGroup>
    <WasmNativeAsset Include="../../runtime/artifacts/bin/native/net6.0-Browser-Release-wasm/dotnet.wasm"/>
</ItemGroup>

— but looks like now it ignores my custom wasm module and just uses the built-in one. Maybe it's overriding it at some point after...

KalleOlaviNiemitalo commented 2 years ago

The custom target looks like it will also remove your custom dotnet.wasm from WasmNativeAsset. If you move the Include into the target as well, that should fix it.

At this point though, it would be good to examine MSBuild logs and find out for certain what adds dotnet.wasm to WasmNativeAsset. The _GetWasmGenerateAppBundleDependencies target in WasmApp.targets cannot be the only one because it checks whether there is a dotnet.wasm item already and thus could not have added the duplicate item that caused an InvalidOperationException.

elringus commented 2 years ago

Thanks again, you're really helped me here! I've figured the following:

<Target Name="ReplaceWasm" AfterTargets="WasmTriggerPublishApp">
    <ItemGroup>
        <WasmNativeAsset Remove="@(WasmNativeAsset)" Condition="%(Extension) == '.wasm'"/>
        <WasmNativeAsset Include="../../runtime/artifacts/bin/native/net6.0-Browser-Release-wasm/dotnet.wasm"/>
    </ItemGroup>
</Target>

— works fine, it uses my module, but it seems to replace the module after trimming and AOT compilation steps. So when not using AOT the only downside is that the module is not trimmed (which is not a big deal), but when running the AOT, the custom module (which is just dotnet runtime) is replaced on top of the compiled module with all the project sources.

I'll dig deeper into msbuild logs and try to figure at which point to replace the module, so it's processed with the trimming and AOT.

elringus commented 2 years ago

The following replaces the wasm before it's used by the other targets:

<Target Name="ReplaceWasm" AfterTargets="WasmBuildApp">
    <ItemGroup>
        <WasmNativeAsset Remove="@(WasmNativeAsset)" Condition="%(Extension) == '.wasm'"/>
        <WasmNativeAsset Include="../../runtime/artifacts/bin/native/net6.0-Browser-Release-wasm/dotnet.wasm"/>
    </ItemGroup>
</Target>

— which is what I've been looking for. However, seems like I can't just provide a custom wasm and expect it to relink properly when trimming or AOTing, as it produce the following error when used:

failed to asynchronously prepare wasm: TypeError: WebAssembly.instantiate(): Import #0 module="a" error: module is not an object or function
TypeError: WebAssembly.instantiate(): Import #0 module="a" error: module is not an object or function

Though I guess this is out of scope for this repository. I'd appreciate if someone from the maintainers could verify that I'm replacing the wasm correctly and the behavior is expected, but otherwise the issue can be closed.

javiercn commented 2 years ago

@Elringus thanks for contacting us.

This is not an scenario that we support. We would recommend you leverage the targets in the the .NET SDK directly rather than attaching to our targets in this way, as this is something that we might change in future releases.

elringus commented 2 years ago

Got it, thanks for the info!