diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets
index 5df409f12a2591..4ff614b0c50611 100644
--- a/eng/liveBuilds.targets
+++ b/eng/liveBuilds.targets
@@ -222,6 +222,13 @@
+
+
+ Version="$(PackageVersionForWorkloadManifests)" />
diff --git a/eng/testing/workloads-testing.targets b/eng/testing/workloads-testing.targets
index d7c03e320e4dd7..541e476fad3d9a 100644
--- a/eng/testing/workloads-testing.targets
+++ b/eng/testing/workloads-testing.targets
@@ -138,7 +138,7 @@
<_SdkWithWorkloadToInstall Include="@(WorkloadCombinationsToInstall)" />
<_SdkWithWorkloadToInstall InstallPath="$(_SdkForWorkloadTestingBasePath)\dotnet-%(Identity)" />
- <_SdkWithWorkloadToInstall StampPath="%(InstallPath)\.workload-installed.stamp" />
+ <_SdkWithWorkloadToInstall StampPath="%(InstallPath)\.workload-installed.$(RuntimeIdentifier).stamp" />
diff --git a/src/libraries/Directory.Build.props b/src/libraries/Directory.Build.props
index fee272a9edca66..3086bd2d3043db 100644
--- a/src/libraries/Directory.Build.props
+++ b/src/libraries/Directory.Build.props
@@ -94,8 +94,9 @@
$(SdkWithNoWorkloadForTestingPath)version-$(SdkVersionForWorkloadTesting).stamp
$(SdkWithNoWorkloadForTestingPath)workload.stamp
- $(ArtifactsBinDir)dotnet-net7+latest\
- $([MSBuild]::NormalizeDirectory($(SdkWithWorkloadForTestingPath)))
+ $(ArtifactsBinDir)dotnet-net7+latest\
+ $(ArtifactsBinDir)dotnet-latest\
+ $([MSBuild]::NormalizeDirectory($(SdkWithWorkloadForTestingPath)))
$(SdkWithWorkloadForTestingPath)version-$(SdkVersionForWorkloadTesting).stamp
$(SdkWithWorkloadForTestingPath)workload.stamp
diff --git a/src/libraries/sendtohelix-wasi.targets b/src/libraries/sendtohelix-wasi.targets
index 2ceec87c66e47e..9998b62ecfd590 100644
--- a/src/libraries/sendtohelix-wasi.targets
+++ b/src/libraries/sendtohelix-wasi.targets
@@ -1,23 +1,49 @@
+
+
true
- wasmtime
+ wasmtime
true
+ <_ShippingPackagesPath>$([MSBuild]::NormalizeDirectory($(ArtifactsDir), 'packages', $(Configuration), 'Shipping'))
PrepareHelixCorrelationPayload_Wasi;
- _AddWorkItemsForLibraryTests
+ _AddWorkItemsForLibraryTests;
+ _AddWorkItemsForBuildWasmApps
- $(BuildHelixWorkItemsDependsOn);PrepareForBuildHelixWorkItems_Wasi
+ $(BuildHelixWorkItemsDependsOn);StageWasiSdkForHelix;PrepareForBuildHelixWorkItems_Wasi
false
false
- false
+ $(RepoRoot)src\mono\wasi\wasi-sdk\
+ $([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'src', 'mono', 'wasi', 'wasi-sdk'))
-
+ true
+ true
+ true
true
false
true
+
+ dotnet-latest
+ dotnet-none
@@ -25,6 +51,7 @@
+
@@ -32,45 +59,49 @@
+
-
+
+ <_XUnitTraitArg Condition="'$(TestUsingWorkloads)' == 'true'">-notrait category=no-workload
+ <_XUnitTraitArg Condition="'$(TestUsingWorkloads)' != 'true'">-trait category=no-workload
+
-
- $(IsRunningLibraryTests)
+
+
+
-
- true
-
+
+
-
-
-
- true
-
- true
- false
-
-
-
-
- true
- false
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+ true
+ false
+
-
+
+
-
+
+
+
+
@@ -107,4 +138,51 @@
+
+
+
+ Workloads-
+ NoWorkload-
+
+
+
+
+ <_WasmWorkItem Include="$(WorkItemArchiveWildCard)" Exclude="$(HelixCorrelationPayload)" />
+
+
+ <_BuildWasmAppsPayloadArchive>@(_WasmWorkItem)
+
+
+
+
+ $(_BuildWasmAppsPayloadArchive)
+ $(HelixCommand)
+ $(_workItemTimeout)
+
+
+
+ $(_BuildWasmAppsPayloadArchive)
+ $(HelixCommand)
+ $(_workItemTimeout)
+
+
+
+
+
+
+
+
+
+
+
+ <_WasiSdkFiles Include="$(WASI_SDK_PATH)\**\*" Exclude="$(WASI_SDK_PATH)\.git\**\*" />
+
+
+
+
diff --git a/src/libraries/sendtohelix.proj b/src/libraries/sendtohelix.proj
index 980c63e6ceb93f..eee6da49b45832 100644
--- a/src/libraries/sendtohelix.proj
+++ b/src/libraries/sendtohelix.proj
@@ -81,7 +81,7 @@
<_TestUsingWorkloadsValues Include="true;false" />
- <_TestUsingWebcilValues Include="true;false" />
+ <_TestUsingWebcilValues Include="true;false" Condition="'$(TargetOS)' == 'browser'" />
<_TestUsingCrossProductValuesTemp Include="@(_TestUsingWorkloadsValues)">
diff --git a/src/libraries/sendtohelixhelp.proj b/src/libraries/sendtohelixhelp.proj
index b2e072c486c843..e51814d7909781 100644
--- a/src/libraries/sendtohelixhelp.proj
+++ b/src/libraries/sendtohelixhelp.proj
@@ -128,8 +128,9 @@
-
-
+
+
+
diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj
index aa30ee2bdb52ea..4fd91574273a9a 100644
--- a/src/libraries/tests.proj
+++ b/src/libraries/tests.proj
@@ -592,6 +592,13 @@
('$(ContinuousIntegrationBuild)' != 'true' and '$(TestAssemblies)' == 'true'))"
BuildInParallel="false" />
+
+
+
+
+
+ Provides the tasks+targets, for consumption by wasi based workloads
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(IntermediateOutputPath)Sdk.targets
+
+
+
+ <_ReplacementValue Include="TargetFrameworkForNETCoreTasks" Value="$(TargetFrameworkForNETCoreTasks)" />
+ <_ReplacementValue Include="TargetFrameworkForNETFrameworkTasks" Value="$(TargetFrameworkForNETFrameworkTasks)" />
+
+
+
+
+
+
+
+ <_WasmAppHostFiles Include="$(WasmAppHostDir)\*" TargetPath="WasmAppHost" />
+
+
+
+
+
+
+
+
diff --git a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Sdk/AutoImport.props b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Sdk/AutoImport.props
new file mode 100644
index 00000000000000..a30a907e69f379
--- /dev/null
+++ b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Sdk/AutoImport.props
@@ -0,0 +1,6 @@
+
+
+
+ net8.0
+
+
diff --git a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Sdk/Sdk.props b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Sdk/Sdk.props
new file mode 100644
index 00000000000000..f6a1d14ac17c2d
--- /dev/null
+++ b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Sdk/Sdk.props
@@ -0,0 +1,9 @@
+
+
+ wasm
+ wasi
+ true
+ Exe
+ true
+
+
diff --git a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Sdk/Sdk.targets.in b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Sdk/Sdk.targets.in
new file mode 100644
index 00000000000000..9fd83ffe152b55
--- /dev/null
+++ b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Sdk/Sdk.targets.in
@@ -0,0 +1,14 @@
+
+
+
+ <_TasksDir Condition="'$(MSBuildRuntimeType)' == 'Core'">$(MSBuildThisFileDirectory)..\tasks\${TargetFrameworkForNETCoreTasks}\
+ <_TasksDir Condition="'$(MSBuildRuntimeType)' != 'Core'">$(MSBuildThisFileDirectory)..\tasks\${TargetFrameworkForNETFrameworkTasks}\
+
+ $(_TasksDir)WasmAppBuilder.dll
+ $(_TasksDir)WasmBuildTasks.dll
+ $([MSBuild]::NormalizeDirectory($(MSBuildThisFileDirectory), '..', 'WasmAppHost'))
+
+
+
+
+
diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest.pkgproj b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest.pkgproj
index 1891a2aa206f19..97cffee7065eb9 100644
--- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest.pkgproj
+++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest.pkgproj
@@ -17,11 +17,13 @@
$(IntermediateOutputPath)WorkloadManifest.json
$(IntermediateOutputPath)WorkloadManifest.targets
+ $(IntermediateOutputPath)WorkloadManifest.Wasi.targets
+
@@ -56,6 +58,11 @@
TemplateFile="WorkloadManifest.targets.in"
Properties="@(_WorkloadManifestValues)"
OutputPath="$(WorkloadManifestTargetsPath)" />
+
+
diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.Wasi.targets.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.Wasi.targets.in
new file mode 100644
index 00000000000000..f6197d5e269b4b
--- /dev/null
+++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.Wasi.targets.in
@@ -0,0 +1,10 @@
+
+
+
+ true
+
+
+
+ $(WasiNativeWorkloadAvailable)
+
+
diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.json.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.json.in
index 90a30c8d3d944c..89911d331bfd62 100644
--- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.json.in
+++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.json.in
@@ -24,6 +24,16 @@
"extends": [ "wasm-tools" ],
"platforms": [ "win-x64", "win-arm64", "linux-x64", "osx-x64", "osx-arm64" ]
},
+ "wasi-experimental": {
+ "description": ".NET WASI experimental",
+ "packs": [
+ "Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk",
+ "Microsoft.NETCore.App.Runtime.Mono.wasi-wasm",
+ "Microsoft.NET.Runtime.WebAssembly.Templates"
+ ],
+ "extends": [ "microsoft-net-runtime-mono-tooling" ],
+ "platforms": [ "win-x64", "win-arm64", "linux-x64", "osx-x64", "osx-arm64" ]
+ },
"microsoft-net-runtime-android": {
"abstract": true,
"description": "Android Mono Runtime",
@@ -155,6 +165,10 @@
"kind": "Sdk",
"version": "${PackageVersion}"
},
+ "Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk": {
+ "kind": "Sdk",
+ "version": "${PackageVersion}"
+ },
"Microsoft.NET.Runtime.WebAssembly.Templates": {
"kind": "template",
"version": "${PackageVersion}"
@@ -354,6 +368,10 @@
"kind": "framework",
"version": "${PackageVersion}"
},
+ "Microsoft.NETCore.App.Runtime.Mono.wasi-wasm" : {
+ "kind": "framework",
+ "version": "${PackageVersion}"
+ },
"Microsoft.NETCore.App.Runtime.win-x64" : {
"kind": "framework",
"version": "${PackageVersion}"
diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.targets.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.targets.in
index fe202f8657a7cc..6dc95f3afc3434 100644
--- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.targets.in
+++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.targets.in
@@ -14,6 +14,8 @@
$(WasmNativeWorkload8)
+
+
<_BrowserWorkloadNotSupportedForTFM Condition="$([MSBuild]::VersionLessThan($(TargetFrameworkVersion), '6.0'))">true
<_BrowserWorkloadDisabled>$(_BrowserWorkloadNotSupportedForTFM)
@@ -111,13 +113,23 @@
-
+
+
+
+
+
+
+
+
<_MonoWorkloadTargetsMobile>true
<_MonoWorkloadRuntimePackPackageVersion>$(_RuntimePackInWorkloadVersionCurrent)
+
+ %(RuntimePackRuntimeIdentifiers);wasi-wasm
+
$(_MonoWorkloadRuntimePackPackageVersion)
Microsoft.NETCore.App.Runtime.Mono.multithread.**RID**
diff --git a/src/mono/nuget/mono-packages.proj b/src/mono/nuget/mono-packages.proj
index ccf9379269923d..e10d9c99454432 100644
--- a/src/mono/nuget/mono-packages.proj
+++ b/src/mono/nuget/mono-packages.proj
@@ -10,6 +10,11 @@
+
+
+
+
+
diff --git a/src/mono/wasi/Makefile b/src/mono/wasi/Makefile
index d505e8ace0f15c..2905f09f803c7d 100644
--- a/src/mono/wasi/Makefile
+++ b/src/mono/wasi/Makefile
@@ -10,6 +10,7 @@ endif
DOTNET=$(TOP)/dotnet.sh
+WASI_SDK_PATH?=$(TOP)/src/mono/wasi/wasi-sdk
CONFIG?=Release
BINDIR?=$(TOP)/artifacts/bin
OBJDIR?=$(TOP)/artifacts/obj
@@ -60,7 +61,7 @@ run-tests-%:
$(DOTNET) build $(TOP)/src/libraries/$*/tests/ /t:Test $(_MSBUILD_WASM_BUILD_ARGS) $(MSBUILD_ARGS)
run-build-tests:
- $(DOTNET) build $(TOP)/src/mono/wasi//Wasi.Build.Tests/ /t:Test /p:Configuration=$(CONFIG) $(MSBUILD_ARGS)
+ WASI_SDK_PATH=$(WASI_SDK_PATH) $(DOTNET) build $(TOP)/src/mono/wasi//Wasi.Build.Tests/ /t:Test /bl $(_MSBUILD_WASM_BUILD_ARGS) $(MSBUILD_ARGS)
build-debugger-tests-helix:
$(DOTNET) build -restore -bl:$(TOP)/artifacts/log/$(CONFIG)/Wasm.Debugger.Tests.binlog \
@@ -78,6 +79,18 @@ submit-debugger-tests-helix: build-debugger-tests-helix
$(_MSBUILD_WASM_BUILD_ARGS) \
$(MSBUILD_ARGS)
+submit-wbt-helix:
+ PATH="$(JSVU):$(PATH)" \
+ $(DOTNET) build $(TOP)/src/mono/wasi/Wasi.Build.Tests/ /v:m /p:ArchiveTests=true /t:ArchiveTests $(_MSBUILD_WASM_BUILD_ARGS) $(MSBUILD_ARGS) && \
+ WASI_SDK_PATH=$(WASI_SDK_PATH) BUILD_REASON=wasm-test SYSTEM_TEAMPROJECT=public BUILD_REPOSITORY_NAME=dotnet/runtime BUILD_SOURCEBRANCH=main \
+ $(TOP)/eng/common/msbuild.sh --ci -restore $(TOP)/src/libraries/sendtohelix.proj \
+ /p:TestRunNamePrefixSuffix=WasiBuildTests /p:HelixBuild=`date "+%Y%m%d.%H%M"` /p:Creator=`whoami` \
+ /bl:$(TOP)/artifacts/log/$(CONFIG)/SendToHelix.binlog -v:m -p:HelixTargetQueue=$(HELIX_TARGET_QUEUE) \
+ /p:RuntimeFlavor=mono /p:TargetRuntimeIdentifier= /p:MonoForceInterpreter= /p:TestScope=innerloop \
+ /p:_Scenarios=buildwasmapps \
+ $(_MSBUILD_WASM_BUILD_ARGS) \
+ $(MSBUILD_ARGS)
+
submit-tests-helix:
echo "\n** This will submit all the available test zip files to helix **\n"
BUILD_REASON=wasm-test SYSTEM_TEAMPROJECT=public BUILD_REPOSITORY_NAME=dotnet/runtime BUILD_SOURCEBRANCH=main \
diff --git a/src/mono/wasi/Wasi.Build.Tests/BuildTestBase.cs b/src/mono/wasi/Wasi.Build.Tests/BuildTestBase.cs
new file mode 100644
index 00000000000000..a66b793a08ed02
--- /dev/null
+++ b/src/mono/wasi/Wasi.Build.Tests/BuildTestBase.cs
@@ -0,0 +1,713 @@
+// 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.Collections.Generic;
+using System.Data;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Xml;
+using Xunit;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+#nullable enable
+
+// [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]
+
+namespace Wasm.Build.Tests
+{
+ public abstract class BuildTestBase : IClassFixture, IDisposable
+ {
+ public const string DefaultTargetFramework = "net8.0";
+ protected static readonly bool s_skipProjectCleanup;
+ protected static readonly string s_xharnessRunnerCommand;
+ protected string? _projectDir;
+ protected readonly ITestOutputHelper _testOutput;
+ protected string _logPath;
+ protected bool _enablePerTestCleanup = false;
+ protected SharedBuildPerTestClassFixture _buildContext;
+ protected string _nugetPackagesDir = string.Empty;
+
+ // FIXME: use an envvar to override this
+ protected static int s_defaultPerTestTimeoutMs = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 30*60*1000 : 15*60*1000;
+ protected static BuildEnvironment s_buildEnv;
+ private const string s_runtimePackPathPattern = "\\*\\* MicrosoftNetCoreAppRuntimePackDir : '([^ ']*)'";
+ private const string s_nugetInsertionTag = "";
+ private static Regex s_runtimePackPathRegex;
+ private static int s_testCounter;
+ private readonly int _testIdx;
+
+ public static bool IsUsingWorkloads => s_buildEnv.IsWorkload;
+ public static bool IsNotUsingWorkloads => !s_buildEnv.IsWorkload;
+ public static string GetNuGetConfigPathFor(string targetFramework) =>
+ Path.Combine(BuildEnvironment.TestDataPath, "nuget8.config"); // for now - we are still using net7, but with
+ // targetFramework == "net7.0" ? "nuget7.config" : "nuget8.config");
+
+ static BuildTestBase()
+ {
+ try
+ {
+ s_buildEnv = new BuildEnvironment();
+ if (EnvironmentVariables.WasiSdkPath is null)
+ throw new Exception($"Error: WASI_SDK_PATH is not set");
+
+ s_buildEnv.EnvVars["WASI_SDK_PATH"] = EnvironmentVariables.WasiSdkPath;
+ s_runtimePackPathRegex = new Regex(s_runtimePackPathPattern);
+
+ s_skipProjectCleanup = !string.IsNullOrEmpty(EnvironmentVariables.SkipProjectCleanup) && EnvironmentVariables.SkipProjectCleanup == "1";
+
+ if (string.IsNullOrEmpty(EnvironmentVariables.XHarnessCliPath))
+ s_xharnessRunnerCommand = "xharness";
+ else
+ s_xharnessRunnerCommand = EnvironmentVariables.XHarnessCliPath;
+
+ Console.WriteLine ("");
+ Console.WriteLine ($"==============================================================================================");
+ Console.WriteLine ($"=============== Running with {(s_buildEnv.IsWorkload ? "Workloads" : "No workloads")} ===============");
+ Console.WriteLine ($"==============================================================================================");
+ Console.WriteLine ("");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine ($"Exception: {ex}");
+ throw;
+ }
+ }
+
+ public BuildTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
+ {
+ _testIdx = Interlocked.Increment(ref s_testCounter);
+ _buildContext = buildContext;
+ _testOutput = output;
+ _logPath = s_buildEnv.LogRootPath; // FIXME:
+ }
+
+ public static IEnumerable> ConfigWithAOTData(bool aot, string? config=null)
+ {
+ if (config == null)
+ {
+ return new IEnumerable
diff --git a/src/mono/wasi/build/WasiSdk.Defaults.props b/src/mono/wasi/build/WasiSdk.Defaults.props
index 51ce6b50a2eff0..67e2e513802001 100644
--- a/src/mono/wasi/build/WasiSdk.Defaults.props
+++ b/src/mono/wasi/build/WasiSdk.Defaults.props
@@ -1,6 +1,7 @@
$(WASI_SDK_PATH)
+ $([MSBuild]::NormalizeDirectory($(WasiSdkRoot), 'share', 'wasi-sysroot'))
$(WasiSdkRoot)\bin\clang
$(WasiClang).exe
diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs
index 6d88eb47a25635..cb24f659c96c99 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs
@@ -31,6 +31,12 @@ public class BuildEnvironment
public static readonly string TestDataPath = Path.Combine(AppContext.BaseDirectory, "data");
public static readonly string TmpPath = Path.Combine(AppContext.BaseDirectory, "wbt");
+ public static readonly string DefaultRuntimeIdentifier =
+#if TARGET_WASI
+ "wasi-wasm";
+#else
+ "browser-wasm";
+#endif
private static readonly Dictionary s_runtimePackVersions = new();
@@ -51,11 +57,12 @@ public BuildEnvironment()
if (string.IsNullOrEmpty(sdkForWorkloadPath))
{
// Is this a "local run?
+ string sdkDirName = string.IsNullOrEmpty(EnvironmentVariables.SdkDirName) ? "dotnet-latest" : EnvironmentVariables.SdkDirName;
string probePath = Path.Combine(Path.GetDirectoryName(typeof(BuildEnvironment).Assembly.Location)!,
"..",
"..",
"..",
- "dotnet-net7+latest");
+ sdkDirName);
if (Directory.Exists(probePath))
sdkForWorkloadPath = Path.GetFullPath(probePath);
else
@@ -133,9 +140,9 @@ public BuildEnvironment()
// FIXME: error checks
public string GetRuntimePackVersion(string tfm = BuildTestBase.DefaultTargetFramework) => s_runtimePackVersions[tfm];
public string GetRuntimePackDir(string tfm = BuildTestBase.DefaultTargetFramework)
- => Path.Combine(WorkloadPacksDir, "Microsoft.NETCore.App.Runtime.Mono.browser-wasm", GetRuntimePackVersion(tfm));
+ => Path.Combine(WorkloadPacksDir, $"Microsoft.NETCore.App.Runtime.Mono.{DefaultRuntimeIdentifier}", GetRuntimePackVersion(tfm));
public string GetRuntimeNativeDir(string tfm = BuildTestBase.DefaultTargetFramework)
- => Path.Combine(GetRuntimePackDir(tfm), "runtimes", "browser-wasm", "native");
+ => Path.Combine(GetRuntimePackDir(tfm), "runtimes", DefaultRuntimeIdentifier, "native");
protected static string s_directoryBuildPropsForWorkloads = File.ReadAllText(Path.Combine(TestDataPath, "Workloads.Directory.Build.props"));
protected static string s_directoryBuildTargetsForWorkloads = File.ReadAllText(Path.Combine(TestDataPath, "Workloads.Directory.Build.targets"));
diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs b/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs
index fb86d5599a8e42..d29e9677fcac8b 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Common/EnvironmentVariables.cs
@@ -20,5 +20,6 @@ internal static class EnvironmentVariables
internal static readonly bool ShowBuildOutput = Environment.GetEnvironmentVariable("SHOW_BUILD_OUTPUT") is not null;
internal static readonly bool UseWebcil = Environment.GetEnvironmentVariable("USE_WEBCIL_FOR_TESTS") is "true";
internal static readonly string? SdkDirName = Environment.GetEnvironmentVariable("SDK_DIR_NAME");
+ internal static readonly string? WasiSdkPath = Environment.GetEnvironmentVariable("WASI_SDK_PATH");
}
}
diff --git a/src/mono/wasm/host/Program.cs b/src/mono/wasm/host/Program.cs
index e0fc4b65fb0f66..a5005dce9d0fc4 100644
--- a/src/mono/wasm/host/Program.cs
+++ b/src/mono/wasm/host/Program.cs
@@ -27,6 +27,7 @@ public static async Task Main(string[] args)
RegisterHostHandler(WasmHost.Browser, BrowserHost.InvokeAsync);
RegisterHostHandler(WasmHost.V8, JSEngineHost.InvokeAsync);
RegisterHostHandler(WasmHost.NodeJS, JSEngineHost.InvokeAsync);
+ RegisterHostHandler(WasmHost.Wasmtime, WasiEngineHost.InvokeAsync);
using CancellationTokenSource cts = new();
ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
diff --git a/src/mono/wasm/host/WasmHost.cs b/src/mono/wasm/host/WasmHost.cs
index e3272ae6c6dcae..bf90ba7afb02f2 100644
--- a/src/mono/wasm/host/WasmHost.cs
+++ b/src/mono/wasm/host/WasmHost.cs
@@ -24,5 +24,10 @@ internal enum WasmHost
///
/// Browser
///
- Browser
+ Browser,
+
+ ///
+ /// wasmtime
+ ///
+ Wasmtime
}
diff --git a/src/mono/wasm/host/wasi/WasiEngineArguments.cs b/src/mono/wasm/host/wasi/WasiEngineArguments.cs
new file mode 100644
index 00000000000000..807ecb659264d7
--- /dev/null
+++ b/src/mono/wasm/host/wasi/WasiEngineArguments.cs
@@ -0,0 +1,31 @@
+// 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.Collections.Generic;
+using System.Text.Json;
+
+namespace Microsoft.WebAssembly.AppHost;
+
+internal sealed class WasiEngineArguments
+{
+ public WasmHost Host => CommonConfig.Host;
+ public CommonConfiguration CommonConfig { get; init; }
+
+ public IEnumerable AppArgs => CommonConfig.RemainingArgs;
+
+ public bool IsSingleFileBundle =>
+ CommonConfig.HostProperties.Extra?.TryGetValue("singleFileBundle", out JsonElement singleFileValue) == true&&
+ singleFileValue.GetBoolean();
+
+ public WasiEngineArguments(CommonConfiguration commonConfig)
+ {
+ CommonConfig = commonConfig;
+ }
+
+ public void Validate()
+ {
+ if (CommonConfig.Host is not WasmHost.Wasmtime)
+ throw new ArgumentException($"Internal error: host {CommonConfig.Host} not supported as a jsengine");
+ }
+}
diff --git a/src/mono/wasm/host/wasi/WasiEngineHost.cs b/src/mono/wasm/host/wasi/WasiEngineHost.cs
new file mode 100644
index 00000000000000..f1e0ac2c41ebd6
--- /dev/null
+++ b/src/mono/wasm/host/wasi/WasiEngineHost.cs
@@ -0,0 +1,95 @@
+// 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.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using System.Diagnostics;
+using System.Threading;
+
+namespace Microsoft.WebAssembly.AppHost;
+
+internal sealed class WasiEngineHost
+{
+ private readonly WasiEngineArguments _args;
+ private readonly ILogger _logger;
+
+ public WasiEngineHost(WasiEngineArguments args, ILogger logger)
+ {
+ _args = args;
+ _logger = logger;
+ }
+
+ public static async Task InvokeAsync(CommonConfiguration commonArgs,
+ ILoggerFactory _,
+ ILogger logger,
+ CancellationToken _1)
+ {
+ var args = new WasiEngineArguments(commonArgs);
+ args.Validate();
+ return await new WasiEngineHost(args, logger).RunAsync();
+ }
+
+ private async Task RunAsync()
+ {
+ string[] engineArgs = Array.Empty();
+
+ string engineBinary = _args.Host switch
+ {
+ WasmHost.Wasmtime => "wasmtime",
+ _ => throw new CommandLineException($"Unsupported engine {_args.Host}")
+ };
+
+ if (!FileUtils.TryFindExecutableInPATH(engineBinary, out string? engineBinaryPath, out string? errorMessage))
+ throw new CommandLineException($"Cannot find host {engineBinary}: {errorMessage}");
+
+ if (_args.CommonConfig.Debugging)
+ throw new CommandLineException($"Debugging not supported with {_args.Host}");
+
+ // var runArgsJson = new RunArgumentsJson(applicationArguments: Array.Empty(),
+ // runtimeArguments: _args.CommonConfig.RuntimeArguments);
+ // runArgsJson.Save(Path.Combine(_args.CommonConfig.AppPath, "runArgs.json"));
+
+ var args = new List()
+ {
+ "run",
+ "--dir",
+ "."
+ };
+
+ args.AddRange(engineArgs);
+ args.Add("--");
+
+ if (_args.IsSingleFileBundle)
+ {
+ args.Add($"{Path.GetFileNameWithoutExtension(_args.CommonConfig.HostProperties.MainAssembly)}.wasm");
+ }
+ else
+ {
+ // FIXME: maybe move the assembly name to a config file
+ args.Add("dotnet.wasm");
+ args.Add(Path.GetFileNameWithoutExtension(_args.CommonConfig.HostProperties.MainAssembly));
+ }
+
+ args.AddRange(_args.AppArgs);
+
+ ProcessStartInfo psi = new()
+ {
+ FileName = engineBinary,
+ WorkingDirectory = _args.CommonConfig.AppPath
+ };
+
+ foreach (string? arg in args)
+ psi.ArgumentList.Add(arg!);
+
+ int exitCode = await Utils.TryRunProcess(psi,
+ _logger,
+ msg => { if (msg != null) _logger.LogInformation(msg); },
+ msg => { if (msg != null) _logger.LogInformation(msg); });
+
+ return exitCode;
+ }
+}
diff --git a/src/mono/wasm/templates/templates/wasi-console/.template.config/template.json b/src/mono/wasm/templates/templates/wasi-console/.template.config/template.json
new file mode 100644
index 00000000000000..b75381b1c74688
--- /dev/null
+++ b/src/mono/wasm/templates/templates/wasi-console/.template.config/template.json
@@ -0,0 +1,34 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "Microsoft",
+ "classifications": [ "Wasi", "WasiConsole" ],
+ "groupIdentity": "Wasi.Console",
+ "precedence": 8000,
+ "identity": "Wasi.Console.8.0",
+ "name": "Wasi Console App",
+ "description": "A project template for creating a .NET app that runs on a WASI runtime",
+ "shortName": "wasiconsole",
+ "sourceName": "wasi-console.0",
+ "preferNameDirectory": true,
+ "tags": {
+ "language": "C#",
+ "type": "project"
+ },
+ "symbols": {
+ "framework": {
+ "type": "parameter",
+ "description": "The target framework for the project.",
+ "datatype": "choice",
+ "choices": [
+ {
+ "choice": "net8.0",
+ "description": "Target net8.0",
+ "displayName": ".NET 8.0"
+ }
+ ],
+ "defaultValue": "net8.0",
+ "replaces": "netX.0",
+ "displayName": "framework"
+ }
+ }
+}
diff --git a/src/mono/wasm/templates/templates/wasi-console/Program.cs b/src/mono/wasm/templates/templates/wasi-console/Program.cs
new file mode 100644
index 00000000000000..dbca79f35764a3
--- /dev/null
+++ b/src/mono/wasm/templates/templates/wasi-console/Program.cs
@@ -0,0 +1,3 @@
+using System;
+
+Console.WriteLine("Hello, Wasi Console!");
diff --git a/src/mono/wasm/templates/templates/wasi-console/Properties/AssemblyInfo.cs b/src/mono/wasm/templates/templates/wasi-console/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000000000..1e407edc804221
--- /dev/null
+++ b/src/mono/wasm/templates/templates/wasi-console/Properties/AssemblyInfo.cs
@@ -0,0 +1,4 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+[assembly:System.Runtime.Versioning.SupportedOSPlatform("wasi")]
diff --git a/src/mono/wasm/templates/templates/wasi-console/README.md b/src/mono/wasm/templates/templates/wasi-console/README.md
new file mode 100644
index 00000000000000..6dfb33e1eb9542
--- /dev/null
+++ b/src/mono/wasm/templates/templates/wasi-console/README.md
@@ -0,0 +1,25 @@
+## .NET WASI app
+
+## Build
+
+You can build the app from Visual Studio or from the command-line:
+
+```
+dotnet build -c Debug/Release
+```
+
+After building the app, the result is in the `bin/$(Configuration)/netX.0/wasi-wasm/AppBundle` directory.
+
+## Run
+
+You can build the app from Visual Studio or the command-line:
+
+```
+dotnet run -c Debug/Release
+```
+
+Or directly start node from the AppBundle directory:
+
+```
+wasmtime bin/$(Configuration)/netX.0/browser-wasm/AppBundle/wasi-console.0.wasm
+```
diff --git a/src/mono/wasm/templates/templates/wasi-console/runtimeconfig.template.json b/src/mono/wasm/templates/templates/wasi-console/runtimeconfig.template.json
new file mode 100644
index 00000000000000..1647a418b8ed90
--- /dev/null
+++ b/src/mono/wasm/templates/templates/wasi-console/runtimeconfig.template.json
@@ -0,0 +1,10 @@
+{
+ "wasmHostProperties": {
+ "perHostConfig": [
+ {
+ "name": "wasmtime",
+ "Host": "wasmtime"
+ }
+ ]
+ }
+}
diff --git a/src/mono/wasm/templates/templates/wasi-console/wasi-console.0.csproj b/src/mono/wasm/templates/templates/wasi-console/wasi-console.0.csproj
new file mode 100644
index 00000000000000..3b897107f39435
--- /dev/null
+++ b/src/mono/wasm/templates/templates/wasi-console/wasi-console.0.csproj
@@ -0,0 +1,8 @@
+
+
+ netX.0
+ wasi-wasm
+ Exe
+ true
+
+
diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilderBaseTask.cs b/src/tasks/WasmAppBuilder/WasmAppBuilderBaseTask.cs
index 11d0f7fd9d6acb..7ecf89b8c36e1b 100644
--- a/src/tasks/WasmAppBuilder/WasmAppBuilderBaseTask.cs
+++ b/src/tasks/WasmAppBuilder/WasmAppBuilderBaseTask.cs
@@ -68,7 +68,7 @@ public override bool Execute()
protected virtual bool ValidateArguments() => true;
- protected void ProcessSatelliteAssemblies(Action<(string fullPath, string culture)> fn)
+ protected void ProcessSatelliteAssemblies(Action<(string fullPath, string culture)> addSatelliteAssemblyFunc)
{
foreach (var assembly in SatelliteAssemblies)
{
@@ -81,7 +81,7 @@ protected void ProcessSatelliteAssemblies(Action<(string fullPath, string cultur
}
// FIXME: validate the culture?
- fn((fullPath, culture));
+ addSatelliteAssemblyFunc((fullPath, culture));
}
}
diff --git a/src/tasks/WasmAppBuilder/wasi/EmitWasmBundleObjectFile.cs b/src/tasks/WasmAppBuilder/wasi/EmitWasmBundleObjectFile.cs
index 4a7e1009dd72d5..2695e526e211ef 100644
--- a/src/tasks/WasmAppBuilder/wasi/EmitWasmBundleObjectFile.cs
+++ b/src/tasks/WasmAppBuilder/wasi/EmitWasmBundleObjectFile.cs
@@ -85,7 +85,7 @@ public override bool Execute()
if (BuildEngine is IBuildEngine9 be9)
allowedParallelism = be9.RequestCores(allowedParallelism);
- Parallel.For(0, remainingObjectFilesToBundle.Length, new ParallelOptions { MaxDegreeOfParallelism = allowedParallelism, CancellationToken = BuildTaskCancelled.Token }, i =>
+ Parallel.For(0, remainingObjectFilesToBundle.Length, new ParallelOptions { MaxDegreeOfParallelism = allowedParallelism, CancellationToken = BuildTaskCancelled.Token }, (i, state) =>
{
var objectFile = remainingObjectFilesToBundle[i];
@@ -100,7 +100,8 @@ public override bool Execute()
Log.LogMessage(MessageImportance.High, "{0}/{1} Bundling {2}...", count, remainingObjectFilesToBundle.Length, Path.GetFileName(contentSourceFile.ItemSpec));
}
- EmitObjectFile(contentSourceFile, outputFile);
+ if (!EmitObjectFile(contentSourceFile, outputFile))
+ state.Stop();
});
}
@@ -109,23 +110,55 @@ public override bool Execute()
return !Log.HasLoggedErrors;
}
- private void EmitObjectFile(ITaskItem fileToBundle, string destinationObjectFile)
+ private bool EmitObjectFile(ITaskItem fileToBundle, string destinationObjectFile)
{
Log.LogMessage(MessageImportance.Low, "Bundling {0} as {1}", fileToBundle.ItemSpec, destinationObjectFile);
if (Path.GetDirectoryName(destinationObjectFile) is string destDir && !string.IsNullOrEmpty(destDir))
Directory.CreateDirectory(destDir);
+ object syncObj = new();
+ StringBuilder outputBuilder = new();
var clangProcess = Process.Start(new ProcessStartInfo
{
FileName = ClangExecutable,
Arguments = $"-xc -o \"{destinationObjectFile}\" -c -",
RedirectStandardInput = true,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
UseShellExecute = false,
})!;
- BundleFileToCSource(destinationObjectFile, fileToBundle, clangProcess.StandardInput.BaseStream);
- clangProcess.WaitForExit();
+ clangProcess.ErrorDataReceived += (sender, e) =>
+ {
+ lock (syncObj)
+ {
+ if (!string.IsNullOrEmpty(e.Data))
+ outputBuilder.AppendLine(e.Data);
+ }
+ };
+ clangProcess.OutputDataReceived += (sender, e) =>
+ {
+ lock (syncObj)
+ {
+ if (!string.IsNullOrEmpty(e.Data))
+ outputBuilder.AppendLine(e.Data);
+ }
+ };
+ clangProcess.BeginOutputReadLine();
+ clangProcess.BeginErrorReadLine();
+
+ try
+ {
+ BundleFileToCSource(destinationObjectFile, fileToBundle, clangProcess.StandardInput.BaseStream);
+ clangProcess.WaitForExit();
+ return true;
+ }
+ catch (IOException ioex)
+ {
+ Log.LogError($"Failed to compile because {ioex.Message}{Environment.NewLine}Output: {outputBuilder}");
+ return false;
+ }
}
private static string GetBundleFileApiSource(ICollection> bundledFilesByObjectFileName)