diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index e602f23f060464..df6a2a54846efe 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -248,6 +248,9 @@ $(HostSharedFrameworkDir)dotnet.native.js; $(HostSharedFrameworkDir)dotnet.native.wasm; " + Exclude="$(LibrariesSharedFrameworkDir)libminipal.a; + $(LibrariesSharedFrameworkDir)libSystem.IO.Compression.Native.a; + $(LibrariesSharedFrameworkDir)libz.a" IsNative="true" /> + + diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj index f8e03ff1dc0b71..6b7483cc170f4a 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj @@ -14,6 +14,8 @@ NetCore.SharedFramework true true + + true diff --git a/src/libraries/sendtohelix-browser.targets b/src/libraries/sendtohelix-browser.targets index 3af0478473c414..34f936a2f6d7db 100644 --- a/src/libraries/sendtohelix-browser.targets +++ b/src/libraries/sendtohelix-browser.targets @@ -158,6 +158,7 @@ + @@ -165,6 +166,7 @@ + @@ -283,6 +285,24 @@ + + + <_CoreCLRWbtMinipalPayloadDir>$(ArtifactsObjDir)helix-staging\coreclr-wbt-minipal\ + + + <_CoreCLRWbtMinipalFiles Include="$(RepoRoot)src\native\minipal\utils.h" /> + + + + + + diff --git a/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets b/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets index ab69d2c3d8465b..8a06d1d068f24a 100644 --- a/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets +++ b/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets @@ -74,15 +74,27 @@ - + + + + + true + + + + + DependsOnTargets="_CoreCLRSetWasmBuildNativeDefaults;_CoreCLRWasmBuildAppCore" /> @@ -153,6 +165,7 @@ <_WasmIntermediateOutputPath Condition="'$(WasmBuildingForNestedPublish)' == 'true'">$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm', 'for-publish')) <_WasmCompileRsp>$(_WasmIntermediateOutputPath)emcc-compile.rsp + <_WasmCompileRspGenerated>$(_WasmIntermediateOutputPath)emcc-compile-generated.rsp <_WasmLinkRsp>$(_WasmIntermediateOutputPath)emcc-link.rsp @@ -270,11 +283,29 @@ <_EmccCFlags Include="@(_EmccCommonFlags)" /> <_EmccCFlags Include="-DGEN_PINVOKE=1" /> <_EmccCFlags Include="$(EmccExtraCFlags)" /> + - - <_EmccCFlags Include="-I"$(RepoRoot)src/coreclr/vm/wasm"" Condition="Exists('$(RepoRoot)src/coreclr/vm/wasm/callhelpers.hpp')" /> - <_EmccCFlags Include="-I"$(RepoRoot)src/native"" Condition="Exists('$(RepoRoot)src/native/minipal/entrypoints.h')" /> - <_EmccCFlags Include="-include "$(_WasmIntermediateOutputPath)coreclr_compat.h"" /> + + + <_MinipalIncludeDir Condition="'$(MINIPAL_INCLUDE_DIR)' != ''">$([MSBuild]::EnsureTrailingSlash($([System.IO.Path]::GetFullPath('$(MINIPAL_INCLUDE_DIR)')))) + <_MinipalIncludeDir Condition="'$(_MinipalIncludeDir)' == ''">$([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)..', '..', '..', 'native', 'minipal')) + + + <_EmccCFlagsGenerated Include="@(_EmccCFlags)" /> + <_EmccCFlagsGenerated Include="-include "$(MSBuildThisFileDirectory)coreclr_compat.h"" /> + <_EmccCFlagsGenerated Include="-include "$(_MinipalIncludeDir)utils.h"" /> @@ -282,8 +313,8 @@ <_WasmSourceFileToCompile Remove="@(_WasmSourceFileToCompile)" /> <_WasmSourceFileToCompile Include="@(NativeFileReference)" Condition="'%(Extension)' == '.c' or '%(Extension)' == '.cpp'" /> <_WasmSourceFileToCompile ObjectFile="$(_WasmIntermediateOutputPath)%(FileName).o" /> - - <_WasmSourceFileToCompile Dependencies="$(_WasmCompileRsp);$(_WasmIntermediateOutputPath)coreclr_compat.h" /> + + <_WasmSourceFileToCompile Dependencies="$(_WasmCompileRsp)" /> @@ -352,69 +383,40 @@ cause duplicate symbol errors when the archive object is pulled in for other required symbols (e.g., SystemInteropJS_*). --> - <_WasmSourceFileToCompile Include="$(_WasmPInvokeTablePath)" ObjectFile="$(_WasmIntermediateOutputPath)pinvoke-table.o" - Dependencies="$(_WasmCompileRsp);$(_WasmIntermediateOutputPath)coreclr_compat.h" /> - <_WasmSourceFileToCompile Include="$(_WasmInterpToNativeTablePath)" ObjectFile="$(_WasmIntermediateOutputPath)wasm_m2n_invoke.o" - Dependencies="$(_WasmCompileRsp);$(_WasmIntermediateOutputPath)coreclr_compat.h" /> + <_WasmSourceFileToCompileGenerated Remove="@(_WasmSourceFileToCompileGenerated)" /> + <_WasmSourceFileToCompileGenerated Include="$(_WasmPInvokeTablePath)" ObjectFile="$(_WasmIntermediateOutputPath)pinvoke-table.o" + Dependencies="$(_WasmCompileRspGenerated);$(MSBuildThisFileDirectory)coreclr_compat.h;$(_MinipalIncludeDir)utils.h" /> + <_WasmSourceFileToCompileGenerated Include="$(_WasmInterpToNativeTablePath)" ObjectFile="$(_WasmIntermediateOutputPath)wasm_m2n_invoke.o" + Dependencies="$(_WasmCompileRspGenerated);$(MSBuildThisFileDirectory)coreclr_compat.h;$(_MinipalIncludeDir)utils.h" /> - - - - <_WasmCoreclrCompatHeader>$(_WasmIntermediateOutputPath)coreclr_compat.h - - - <_CompatHeaderLines Include="// Auto-generated CoreCLR compat header for app native build" /> - <_CompatHeaderLines Include="#pragma once" /> - <_CompatHeaderLines Include="#include <stddef.h>" /> - <_CompatHeaderLines Include="#include <stdint.h>" /> - <_CompatHeaderLines Include="#include <stdlib.h>" /> - <_CompatHeaderLines Include="#include <stdio.h>" /> - <_CompatHeaderLines Include="// CoreCLR type stubs" /> - <_CompatHeaderLines Include="#ifndef _CORECLR_COMPAT_TYPES" /> - <_CompatHeaderLines Include="#define _CORECLR_COMPAT_TYPES" /> - <_CompatHeaderLines Include="typedef void MethodDesc%3B" /> - <_CompatHeaderLines Include="typedef uintptr_t PCODE%3B" /> - <_CompatHeaderLines Include="typedef uint32_t ULONG%3B" /> - <_CompatHeaderLines Include="#define INTERP_STACK_SLOT_SIZE 8u" /> - <_CompatHeaderLines Include="#endif" /> - <_CompatHeaderLines Include="// CoreCLR logging stubs" /> - <_CompatHeaderLines Include="#define LF_INTEROP 0" /> - <_CompatHeaderLines Include="#define LL_INFO1000 0" /> - <_CompatHeaderLines Include="#define LOG(x)" /> - <_CompatHeaderLines Include="// CoreCLR assertion stubs" /> - <_CompatHeaderLines Include="#define PORTABILITY_ASSERT(msg) do { fprintf(stderr, "PORTABILITY_ASSERT: %25s", msg)%3B fprintf(stderr, "\n")%3B abort()%3B } while(0)" /> - - - - - - - + <_WasmCFlags Include="@(_EmccCFlags)" /> + <_WasmCFlagsGenerated Include="@(_EmccCFlagsGenerated)" /> - + + - + + - - - <_WasmCompileArguments Remove="@(_WasmCompileArguments)" /> - <_WasmCompileArguments Include=""@$(_WasmCompileRsp)"" Condition="'$(_WasmCompileRsp)' != ''" /> - + + + + + + + <_WasmNativeFileForLinking Include="%(_WasmSourceFileToCompile.ObjectFile)" /> + <_WasmNativeFileForLinking Include="%(_WasmSourceFileToCompileGenerated.ObjectFile)" /> diff --git a/src/mono/browser/build/coreclr_compat.h b/src/mono/browser/build/coreclr_compat.h new file mode 100644 index 00000000000000..f5fb46e96057c5 --- /dev/null +++ b/src/mono/browser/build/coreclr_compat.h @@ -0,0 +1,67 @@ +// Auto-included CoreCLR compat header for app native build. +// +// This header is pre-included via -include when compiling pinvoke-table.cpp +// and wasm_m2n_invoke.g.cpp produced by ManagedToNativeGenerator, so those +// files can be compiled outside the full CoreCLR build context (e.g. in +// Wasm.Build.Tests on Helix where src/coreclr/vm/wasm/callhelpers.hpp is not +// part of the payload). +// +// Macro definitions consumed by generator output (NOINLINE, ARRAY_SIZE, ...) +// come from src/native/minipal/utils.h, which is shipped to the WBT Helix +// payload and force-included alongside this header by +// BrowserWasmApp.CoreCLR.targets. +// +// Definitions for the symbols declared here live in libcoreclr_static.a (which +// is linked in later) or in the same generated .cpp (e.g. g_wasmThunks / +// g_ReverseThunks tables are emitted by the generator itself). + +#pragma once + +#include +#include +#include +#include +#include + +// CoreCLR type stubs +#ifndef _CORECLR_COMPAT_TYPES +#define _CORECLR_COMPAT_TYPES +typedef void MethodDesc; +typedef uintptr_t PCODE; +typedef uint32_t ULONG; +#define INTERP_STACK_SLOT_SIZE 8u +#endif + +// CoreCLR logging stubs +#define LF_INTEROP 0 +#define LL_INFO1000 0 +#define LOG(x) + +// CoreCLR assertion stub +#define PORTABILITY_ASSERT(msg) do { fprintf(stderr, "PORTABILITY_ASSERT: %s\n", msg); abort(); } while(0) + +// Mirrors of declarations from src/coreclr/vm/wasm/callhelpers.hpp. +#define TERMINATE_R2R_STACK_WALK 1 +struct StringToWasmSigThunk { const char* key; void* value; }; +extern const StringToWasmSigThunk g_wasmThunks[]; +extern const size_t g_wasmThunksCount; +struct ReverseThunkMapValue { MethodDesc** Target; void* EntryPoint; }; +struct ReverseThunkMapEntry { ULONG hashCode; const char* Source; ReverseThunkMapValue value; }; +extern const ReverseThunkMapEntry g_ReverseThunks[]; +extern const size_t g_ReverseThunksCount; + +// Mirrors of declarations from src/native/minipal/entrypoints.h, used by +// pinvoke-table.cpp. Marked static inline (rather than the upstream 'static') +// so cpp files that #include this compat header but don't call the helper +// don't trigger -Wunused-function. +typedef struct { const char* name; const void* method; } Entry; +#define DllImportEntry(impl) {#impl, (void*)&impl}, +static inline const void* minipal_resolve_dllimport(const Entry* resolutionTable, size_t tableLength, const char* name) +{ + for (size_t i = 0; i < tableLength; i++) + { + if (strcmp(name, resolutionTable[i].name) == 0) + return resolutionTable[i].method; + } + return NULL; +} diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/AssertBundleOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/AssertBundleOptions.cs index a2794ab4e91f19..266d7d25fc29fb 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/AssertBundleOptions.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/AssertBundleOptions.cs @@ -15,5 +15,11 @@ public record AssertBundleOptions( bool ExpectSymbolsFile = true, bool AssertIcuAssets = true, bool AssertSymbolsFile = true, - bool? ExpectDotnetJsFingerprinting = null + bool? ExpectDotnetJsFingerprinting = null, + // Runtime pack root (the directory that contains runtimes//native/...) as resolved + // by the build, parsed from MSBuild output. When set, ICU asset assertions resolve their + // source paths from here instead of the workload-installed pack dir, so CoreCLR no-workload + // runs (where the publish-time pack comes from a per-test NuGet cache that doesn't match + // the SDK's pre-installed dotnet/packs/ contents) can still be validated correctly. + string? RuntimePackDir = null ); diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs index 77be019fbfaf5b..0b2782f3885ca7 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs @@ -165,6 +165,8 @@ public BuildEnvironment() EnvVars["WASM_APP_BUILDER_TASKS_ASSEMBLY_PATH"] = EnvironmentVariables.WasmAppBuilderTasksAssemblyPath; if (!string.IsNullOrEmpty(EnvironmentVariables.EmsdkPath)) EnvVars["EMSDK_PATH"] = EnvironmentVariables.EmsdkPath; + if (!string.IsNullOrEmpty(EnvironmentVariables.MinipalIncludeDir)) + EnvVars["MINIPAL_INCLUDE_DIR"] = EnvironmentVariables.MinipalIncludeDir; } DotNet = Path.Combine(sdkForWorkloadPath!, "dotnet"); @@ -195,11 +197,19 @@ public string GetRuntimePackVersion(string tfm) : throw new ArgumentException($"No runtime pack version found for tfm={tfm} ."); public string GetRuntimePackDir(string tfm, RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded) - => Path.Combine(WorkloadPacksDir, - runtimeType is RuntimeVariant.SingleThreaded - ? $"Microsoft.NETCore.App.Runtime.Mono.{DefaultRuntimeIdentifier}" - : $"Microsoft.NETCore.App.Runtime.Mono.multithread.{DefaultRuntimeIdentifier}", - GetRuntimePackVersion(tfm)); + => Path.Combine(WorkloadPacksDir, GetRuntimePackName(runtimeType), GetRuntimePackVersion(tfm)); + + private string GetRuntimePackName(RuntimeVariant runtimeType) + { + // CoreCLR ships browser-wasm via Microsoft.NETCore.App.Runtime.{rid} (no flavor segment). + // Mono uses Microsoft.NETCore.App.Runtime.Mono.{rid}, with a separate .multithread. variant. + if (IsCoreClrRuntime) + return $"Microsoft.NETCore.App.Runtime.{DefaultRuntimeIdentifier}"; + + return runtimeType is RuntimeVariant.SingleThreaded + ? $"Microsoft.NETCore.App.Runtime.Mono.{DefaultRuntimeIdentifier}" + : $"Microsoft.NETCore.App.Runtime.Mono.multithread.{DefaultRuntimeIdentifier}"; + } public string GetRuntimeNativeDir(string tfm, RuntimeVariant runtimeType = RuntimeVariant.SingleThreaded) => Path.Combine(GetRuntimePackDir(tfm, runtimeType), "runtimes", DefaultRuntimeIdentifier, "native"); public bool IsMultiThreadingRuntimePackAvailableFor(string tfm) diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs b/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs index c121945a78c226..81a28c8a8928dc 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs @@ -32,5 +32,6 @@ internal static class EnvironmentVariables internal static readonly string? BrowserBuildTargetsDir = Environment.GetEnvironmentVariable("BROWSER_BUILD_TARGETS_DIR"); internal static readonly string? WasmAppBuilderTasksAssemblyPath = Environment.GetEnvironmentVariable("WASM_APP_BUILDER_TASKS_ASSEMBLY_PATH"); internal static readonly string? EmsdkPath = Environment.GetEnvironmentVariable("EMSDK_PATH"); + internal static readonly string? MinipalIncludeDir = Environment.GetEnvironmentVariable("MINIPAL_INCLUDE_DIR"); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/DllImportTests.cs b/src/mono/wasm/Wasm.Build.Tests/DllImportTests.cs index 4d84a6d567dc0e..1d2aa5645cdaaa 100644 --- a/src/mono/wasm/Wasm.Build.Tests/DllImportTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/DllImportTests.cs @@ -14,7 +14,6 @@ namespace Wasm.Build.Tests { - [TestCategory("native")] public class DllImportTests : PInvokeTableGeneratorTestsBase { public DllImportTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) @@ -24,6 +23,9 @@ public DllImportTests(ITestOutputHelper output, SharedBuildPerTestClassFixture b [Theory] [BuildAndRun(aot: false)] + // The "warning ... native function ... varargs" check is emitted by Mono's PInvokeTableGenerator. + // CoreCLR's PInvokeTableGenerator does not emit that warning, so this test is Mono-only. + [TestCategory("mono")] public async Task NativeLibraryWithVariadicFunctions(Configuration config, bool aot) { ProjectInfo info = PrepareProjectForVariadicFunction(config, aot, "variadic"); @@ -176,6 +178,11 @@ private ProjectInfo PrepareProjectForVariadicFunction(Configuration config, bool string extraItems = $""; ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraItems: extraItems, extraProperties: extraProperties); File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", objectFilename), Path.Combine(_projectDir, objectFilename)); + + // The variadic-function test program does not use JS interop, so the JS interop + // assembly would be linked away by the trimmer (CoreCLR-Wasm) and the template + // main.js (which calls getAssemblyExports) would fail at startup. + ReplaceMainJsWithMinimalRunMain(); return info; } } diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs index 2883967abc0105..4975294e26448c 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs @@ -41,13 +41,13 @@ from locale in locales [Theory] [MemberData(nameof(IcuExpectedAndMissingCustomShardTestData), parameters: new object[] { Configuration.Release })] - [TestCategory("native")] + [TestCategory("native-coreclr")] public async Task CustomIcuShard(Configuration config, bool aot, string customIcuPath, string customLocales, bool onlyPredefinedCultures) => await TestIcuShards(config, Template.WasmBrowser, aot, customIcuPath, customLocales, GlobalizationMode.Custom, onlyPredefinedCultures); [Theory] [MemberData(nameof(IcuExpectedAndMissingAutomaticShardTestData), parameters: new object[] { Configuration.Release })] - [TestCategory("native")] + [TestCategory("native-coreclr")] public async Task AutomaticShardSelectionDependingOnEnvLocale(Configuration config, bool aot, string environmentLocale, string testedLocales) => await PublishAndRunIcuTest(config, Template.WasmBrowser, aot, testedLocales, GlobalizationMode.Sharded, locale: environmentLocale); } diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests2.cs b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests2.cs index 7005b99642b21e..fc7b0316dca708 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests2.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuShardingTests2.cs @@ -38,7 +38,7 @@ from locale in locales [Theory] [MemberData(nameof(IcuExpectedAndMissingShardFromRuntimePackTestData), parameters: new object[] { Configuration.Release })] - [TestCategory("native")] + [TestCategory("native-coreclr")] public async Task DefaultAvailableIcuShardsFromRuntimePack(Configuration config, bool aot, string shardName, string testedLocales) => await TestIcuShards(config, Template.WasmBrowser, aot, shardName, testedLocales, GlobalizationMode.Custom); } diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuTests.cs b/src/mono/wasm/Wasm.Build.Tests/IcuTests.cs index f57e229bc0200d..b67e7c1a9bfba1 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuTests.cs @@ -53,7 +53,7 @@ public static IEnumerable IncorrectIcuTestData(Configuration config) [Theory] [MemberData(nameof(FullIcuWithInvariantTestData), parameters: new object[] { Configuration.Release })] - [TestCategory("native")] + [TestCategory("native-coreclr")] public async Task FullIcuFromRuntimePackWithInvariant(Configuration config=Configuration.Release, bool aot=false, bool invariant=true, bool fullIcu=true, string testedLocales="Array.Empty()") => await PublishAndRunIcuTest( config, @@ -67,7 +67,7 @@ await PublishAndRunIcuTest( [Theory] [MemberData(nameof(FullIcuWithICustomIcuTestData), parameters: new object[] { Configuration.Release })] - [TestCategory("native")] + [TestCategory("native-coreclr")] public async Task FullIcuFromRuntimePackWithCustomIcu(Configuration config, bool aot, bool fullIcu) { string customIcuProperty = "BlazorIcuDataFileName"; diff --git a/src/mono/wasm/Wasm.Build.Tests/IcuTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/IcuTestsBase.cs index daf9b7ea597176..ec8fe91fb30fe4 100644 --- a/src/mono/wasm/Wasm.Build.Tests/IcuTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/IcuTestsBase.cs @@ -131,7 +131,10 @@ protected ProjectInfo CreateIcuProject( File.WriteAllText(programPath, programText); _testOutput.WriteLine($"----- Program: -----{Environment.NewLine}{programText}{Environment.NewLine}-------"); - UpdateBrowserMainJs(); + // The template main.js calls JS interop APIs (setModuleImports, getAssemblyExports) + // which the ICU test program does not use. Replace it with a minimal version tailored + // for ICU tests, otherwise the JS interop assembly would be linked away by the trimmer. + ReplaceMainJsWithMinimalRunMain(); return info; } diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs index 9a7950b7a5ec4b..f541eb52287a9f 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs @@ -39,7 +39,7 @@ public async Task AOT_InvariantGlobalization(Configuration config, bool aot, boo // TODO: What else should we use to verify a relinked build? [Theory] [MemberData(nameof(InvariantGlobalizationTestData), parameters: new object[] { /*aot*/ false })] - [TestCategory("native")] + [TestCategory("native-coreclr")] public async Task RelinkingWithoutAOT(Configuration config, bool aot, bool? invariantGlobalization) => await TestInvariantGlobalization(config, aot, invariantGlobalization, isNativeBuild: true); @@ -61,6 +61,10 @@ private async Task TestInvariantGlobalization(Configuration config, bool aot, bo string prefix = $"invariant_{invariantGlobalization?.ToString() ?? "unset"}"; ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, prefix, extraProperties: extraProperties); ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "InvariantGlobalization.cs")); + // The InvariantGlobalization test program does not use JS interop, so the JS interop + // assembly would be linked away by the trimmer (CoreCLR-Wasm) and the template main.js + // (which calls getAssemblyExports) would fail at startup. + ReplaceMainJsWithMinimalRunMain(); var globalizationMode = invariantGlobalization == true ? GlobalizationMode.Invariant : GlobalizationMode.Sharded; PublishProject(info, config, new PublishOptions(GlobalizationMode: globalizationMode, AOT: aot), isNativeBuild: isNativeBuild); diff --git a/src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs b/src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs index d4c4a32c3b6412..e796b8791476e6 100644 --- a/src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs @@ -13,7 +13,7 @@ namespace Wasm.Build.Tests; -[TestCategory("native")] +[TestCategory("native-coreclr")] public class MemoryTests : WasmTemplateTestsBase { public MemoryTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs index ff33906df14d1a..cbabd30b5f0ac1 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeBuildTests.cs @@ -13,7 +13,6 @@ namespace Wasm.Build.Tests { - [TestCategory("native")] public class NativeBuildTests : WasmTemplateTestsBase { public NativeBuildTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) @@ -33,7 +32,10 @@ public async Task SimpleNativeBuild(Configuration config, bool aot) extraProperties: "true"); UpdateBrowserProgramFile(); - UpdateBrowserMainJs(); + // The replacement program does not use JS interop, so the JS interop assembly would be + // linked away by the trimmer (CoreCLR-Wasm) and the template main.js (which calls + // getAssemblyExports) would fail at startup. + ReplaceMainJsWithMinimalRunMain(); (string _, string buildOutput) = PublishProject(info, config, isNativeBuild: true); await RunForPublishWithWebServer(new BrowserRunOptions(config, ExpectedExitCode: 42)); @@ -41,6 +43,7 @@ public async Task SimpleNativeBuild(Configuration config, bool aot) [Theory] [BuildAndRun(aot: true)] + [TestCategory("native")] public void AOTNotSupportedWithNoTrimming(Configuration config, bool aot) { ProjectInfo info = CreateWasmTemplateProject( @@ -59,6 +62,7 @@ public void AOTNotSupportedWithNoTrimming(Configuration config, bool aot) [Theory] [BuildAndRun(config: Configuration.Release, aot: true)] + [TestCategory("native")] public void IntermediateBitcodeToObjectFilesAreNotLLVMIR(Configuration config, bool aot) { string printFileTypeTarget = @" @@ -91,6 +95,7 @@ public void IntermediateBitcodeToObjectFilesAreNotLLVMIR(Configuration config, b [Theory] [BuildAndRun(config: Configuration.Release, aot: true)] + [TestCategory("native")] public void NativeBuildIsRequired(Configuration config, bool aot) { ProjectInfo info = CreateWasmTemplateProject( diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeLibraryTests.cs b/src/mono/wasm/Wasm.Build.Tests/NativeLibraryTests.cs index 9cbe49352c147c..c69ae70797ac40 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeLibraryTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeLibraryTests.cs @@ -11,7 +11,6 @@ namespace Wasm.Build.Tests { - [TestCategory("native")] public class NativeLibraryTests : WasmTemplateTestsBase { public NativeLibraryTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) @@ -21,8 +20,16 @@ public NativeLibraryTests(ITestOutputHelper output, SharedBuildPerTestClassFixtu [Theory] [BuildAndRun(aot: false)] + public Task ProjectWithNativeReference(Configuration config, bool aot) => + ProjectWithNativeReferenceCore(config, aot); + + [Theory] [BuildAndRun(config: Configuration.Release, aot: true)] - public async Task ProjectWithNativeReference(Configuration config, bool aot) + [TestCategory("native")] + public Task ProjectWithNativeReference_AOT(Configuration config, bool aot) => + ProjectWithNativeReferenceCore(config, aot); + + private async Task ProjectWithNativeReferenceCore(Configuration config, bool aot) { string objectFilename = "native-lib.o"; string extraItems = $""; @@ -32,6 +39,10 @@ public async Task ProjectWithNativeReference(Configuration config, bool aot) File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", objectFilename), Path.Combine(_projectDir, objectFilename)); Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "AppUsingNativeLib"), _projectDir, overwrite: true); DeleteFile(Path.Combine(_projectDir, "Common", "Program.cs")); + // The AppUsingNativeLib program does not use JS interop, so the JS interop assembly + // would be linked away by the trimmer (CoreCLR-Wasm) and the template main.js (which + // calls getAssemblyExports) would fail at startup. + ReplaceMainJsWithMinimalRunMain(); (string _, string buildOutput) = PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: true); RunResult output = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun")); @@ -42,9 +53,18 @@ public async Task ProjectWithNativeReference(Configuration config, bool aot) [Theory] [BuildAndRun(aot: false)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/103566")] + public Task ProjectUsingSkiaSharp(Configuration config, bool aot) => + ProjectUsingSkiaSharpCore(config, aot); + + [Theory] [BuildAndRun(config: Configuration.Release, aot: true)] + [TestCategory("native")] [ActiveIssue("https://github.com/dotnet/runtime/issues/103566")] - public async Task ProjectUsingSkiaSharp(Configuration config, bool aot) + public Task ProjectUsingSkiaSharp_AOT(Configuration config, bool aot) => + ProjectUsingSkiaSharpCore(config, aot); + + private async Task ProjectUsingSkiaSharpCore(Configuration config, bool aot) { string prefix = $"AppUsingSkiaSharp"; string extraItems = @$" @@ -62,8 +82,16 @@ public async Task ProjectUsingSkiaSharp(Configuration config, bool aot) [Theory] [BuildAndRun(aot: false)] + public Task ProjectUsingBrowserNativeCrypto(Configuration config, bool aot) => + ProjectUsingBrowserNativeCryptoCore(config, aot); + + [Theory] [BuildAndRun(config: Configuration.Release, aot: true)] - public async Task ProjectUsingBrowserNativeCrypto(Configuration config, bool aot) + [TestCategory("native")] + public Task ProjectUsingBrowserNativeCrypto_AOT(Configuration config, bool aot) => + ProjectUsingBrowserNativeCryptoCore(config, aot); + + private async Task ProjectUsingBrowserNativeCryptoCore(Configuration config, bool aot) { ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "AppUsingBrowserNativeCrypto"); ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "NativeCrypto.cs")); @@ -80,14 +108,26 @@ public async Task ProjectUsingBrowserNativeCrypto(Configuration config, bool aot [Theory] [BuildAndRun(aot: false)] + public Task ProjectWithNativeLibrary(Configuration config, bool aot) => + ProjectWithNativeLibraryCore(config, aot); + + [Theory] [BuildAndRun(config: Configuration.Release, aot: true)] - public async Task ProjectWithNativeLibrary(Configuration config, bool aot) + [TestCategory("native")] + public Task ProjectWithNativeLibrary_AOT(Configuration config, bool aot) => + ProjectWithNativeLibraryCore(config, aot); + + private async Task ProjectWithNativeLibraryCore(Configuration config, bool aot) { string extraItems = "\n"; ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "AppUsingNativeLib-a", extraItems: extraItems); Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "AppUsingNativeLib"), _projectDir, overwrite: true); DeleteFile(Path.Combine(_projectDir, "Common", "Program.cs")); File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "native-lib.o"), Path.Combine(_projectDir, "native-lib.o")); + // The AppUsingNativeLib program does not use JS interop, so the JS interop assembly + // would be linked away by the trimmer (CoreCLR-Wasm) and the template main.js (which + // calls getAssemblyExports) would fail at startup. + ReplaceMainJsWithMinimalRunMain(); (string _, string buildOutput) = PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: true); RunResult output = await RunForPublishWithWebServer(new BrowserRunOptions(config, TestScenario: "DotnetRun", ExpectedExitCode: 0)); diff --git a/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs b/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs index f88a5013b92f95..4e7f6170a827e6 100644 --- a/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs @@ -14,7 +14,6 @@ namespace Wasm.Build.Tests { - [TestCategory("native")] public class PInvokeTableGeneratorTests : PInvokeTableGeneratorTestsBase { public PInvokeTableGeneratorTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) @@ -180,6 +179,9 @@ public async Task UnmanagedCallersOnly_Namespaced(Configuration config, bool aot [Theory] [BuildAndRun()] + // The test fetches WasmAppBuilder.dll from the Microsoft.NET.Runtime.WebAssembly.Sdk + // workload pack, which is not present in the NoWorkload (CoreCLR-Wasm) Helix payload. + [TestCategory("mono")] public void IcallWithOverloadedParametersAndEnum(Configuration config, bool aot) { string appendToTheEnd = @@ -288,6 +290,10 @@ public async Task BuildNativeInNonEnglishCulture(Configuration config, bool aot, ReplaceFile(programRelativePath, Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "BuildNative.cs")); string cCodeFilename = "simple.c"; File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", cCodeFilename), Path.Combine(_projectDir, cCodeFilename)); + // The BuildNative test program does not use JS interop, so the JS interop assembly + // would be linked away by the trimmer (CoreCLR-Wasm) and the template main.js (which + // calls getAssemblyExports) would fail at startup. + ReplaceMainJsWithMinimalRunMain(); var extraEnvVars = new Dictionary { { "LANG", culture }, @@ -351,6 +357,7 @@ private async Task EnsureWasmAbiRulesAreFollowed(Configuration config, bool aot) [Theory] [BuildAndRun(aot: true, config: Configuration.Release)] + [TestCategory("native")] public async Task EnsureWasmAbiRulesAreFollowedInAOT(Configuration config, bool aot) => await EnsureWasmAbiRulesAreFollowed(config, aot); @@ -361,6 +368,7 @@ public async Task EnsureWasmAbiRulesAreFollowedInInterpreter(Configuration confi [Theory] [BuildAndRun(aot: true, config: Configuration.Release)] + [TestCategory("native")] public void EnsureComInteropCompilesInAOT(Configuration config, bool aot) { ProjectInfo info = CopyTestAsset(config, aot, TestAsset.WasmBasicTestApp, "com"); @@ -381,6 +389,10 @@ public async Task UCOWithSpecialCharacters(Configuration config, bool aot) ReplaceFile(Path.Combine("Common", "Program.cs"), Path.Combine(BuildEnvironment.TestAssetsPath, "EntryPoints", "PInvoke", "UnmanagedCallback.cs")); string cCodeFilename = "local.c"; File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", cCodeFilename), Path.Combine(_projectDir, cCodeFilename)); + // The UnmanagedCallback test program does not use JS interop, so the JS interop assembly + // would be linked away by the trimmer (CoreCLR-Wasm) and the template main.js (which + // calls getAssemblyExports) would fail at startup. + ReplaceMainJsWithMinimalRunMain(); PublishProject(info, config, new PublishOptions(AOT: aot), isNativeBuild: true); RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index ceec74289c4f90..74f9e4fdee2b7e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -431,6 +431,18 @@ public static void AssertRuntimePackPath(string buildOutput, string targetFramew throw new XunitException($"Runtime pack path doesn't match.{Environment.NewLine}Expected: '{expectedRuntimePackDir}'{Environment.NewLine}Actual: '{actualPath}'"); } + // Extract the runtime pack root that the build actually resolved (printed by + // BrowserWasmApp.targets / BrowserWasmApp.CoreCLR.targets as + // "** MicrosoftNetCoreAppRuntimePackDir : ''"). Returns null when the line is + // not present (e.g. assertions running off a cached output that didn't capture it). + public static string? TryGetRuntimePackDirFromBuildOutput(string? buildOutput) + { + if (string.IsNullOrEmpty(buildOutput)) + return null; + var match = s_runtimePackPathRegex.Match(buildOutput); + return match.Success && match.Groups.Count == 2 ? match.Groups[1].Value : null; + } + public static void AssertDotNetJsSymbols(AssertBundleOptions assertOptions) { TestUtils.AssertFilesExist(assertOptions.BinFrameworkDir, new[] { "dotnet.native.js.symbols" }, expectToExist: assertOptions.ExpectSymbolsFile); @@ -494,7 +506,13 @@ public void AssertIcuAssets(AssertBundleOptions assertOptions, BootJsonData boot if (assertOptions.BuildOptions.GlobalizationMode is GlobalizationMode.Custom) { string srcPath = assertOptions.BuildOptions.CustomIcuFile!; - string runtimePackDir = BuildTestBase.s_buildEnv.GetRuntimeNativeDir(assertOptions.BuildOptions.TargetFramework, assertOptions.BuildOptions.RuntimeType); + // Prefer the runtime-pack root the build actually resolved (from MSBuild output), + // since that is the same pack the publish output was produced from. The SDK's + // workload-pack dir (GetRuntimeNativeDir) can diverge from it on CoreCLR no-workload + // runs where the publish-time pack comes from a per-test NuGet cache. + string runtimePackDir = !string.IsNullOrEmpty(assertOptions.RuntimePackDir) + ? Path.Combine(assertOptions.RuntimePackDir, "runtimes", BuildEnvironment.DefaultRuntimeIdentifier, "native") + : BuildTestBase.s_buildEnv.GetRuntimeNativeDir(assertOptions.BuildOptions.TargetFramework, assertOptions.BuildOptions.RuntimeType); if (!Path.IsPathRooted(srcPath)) srcPath = Path.Combine(runtimePackDir, assertOptions.BuildOptions.CustomIcuFile!); TestUtils.AssertSameFile(srcPath, actual.Single()); diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs index c5d56480561582..47db58dc97368f 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs @@ -89,6 +89,14 @@ public ProjectInfo CreateWasmTemplateProject( EnsureWasmTemplatesInstalled(); + // [diag] Log DOTNET_CLI_HOME inherited by the `dotnet new