Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions eng/restore-toolset.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Installs .NET SDK workloads needed by projects in this repository.
#
# This script is dot-sourced by eng/common/build.ps1's InitializeCustomToolset
# AFTER InitializeDotNetCli has bootstrapped the repo-local .dotnet/ SDK, so
# $RepoRoot and the helpers from eng/common/tools.ps1 are in scope.
#
# Currently required by samples/WasiPlayground/WasiPlayground.csproj which
# targets net10.0 with <UsingWasiRuntimeWorkload>true</UsingWasiRuntimeWorkload>
# and would otherwise fail with NETSDK1147 in CI.

$RequiredWorkloads = @('wasi-experimental-net10')
Comment thread
Evangelink marked this conversation as resolved.

# Note: `wasm-tools-net10` is documented in samples/WasiPlayground/README.md
# as a prerequisite for `dotnet publish`, but it is not needed by the repo's
# `dotnet build` so we keep the CI install minimal.

Comment thread
Evangelink marked this conversation as resolved.
$dotnetRoot = if (-not [string]::IsNullOrEmpty($env:DOTNET_INSTALL_DIR)) {
$env:DOTNET_INSTALL_DIR
} else {
Join-Path $RepoRoot '.dotnet'
}

$dotnet = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet')

if (-not (Test-Path $dotnet)) {
Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "restore-toolset.ps1: dotnet executable not found at '$dotnet'."
ExitWithExitCode 1
}

# Cheap, network-free probe: parse `dotnet workload list` text to skip already-installed workloads.
$listOutput = & $dotnet workload list 2>&1 | Out-String
$missing = @($RequiredWorkloads | Where-Object {
-not ($listOutput -match "(?m)^\s*$([regex]::Escape($_))\s")
})

if ($missing.Count -eq 0) {
Write-Host "All required workloads already installed: $($RequiredWorkloads -join ', ')"
return
}

Write-Host "Installing .NET SDK workloads: $($missing -join ', ')"
& $dotnet workload install @missing
if ($LASTEXITCODE -ne 0) {
Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install workloads '$($missing -join ', ')' (dotnet workload install exit code $LASTEXITCODE)."
ExitWithExitCode $LASTEXITCODE
Comment thread
Evangelink marked this conversation as resolved.
}
52 changes: 52 additions & 0 deletions eng/restore-toolset.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash

# Installs .NET SDK workloads needed by projects in this repository.
#
# This script is dot-sourced by eng/common/build.sh's InitializeCustomToolset
# AFTER InitializeDotNetCli has bootstrapped the repo-local .dotnet/ SDK, so
# $repo_root and the helpers from eng/common/tools.sh are in scope.
#
# Currently required by samples/WasiPlayground/WasiPlayground.csproj which
# targets net10.0 with <UsingWasiRuntimeWorkload>true</UsingWasiRuntimeWorkload>
# and would otherwise fail with NETSDK1147 in CI.

required_workloads=('wasi-experimental-net10')

# Note: `wasm-tools-net10` is documented in samples/WasiPlayground/README.md
# as a prerequisite for `dotnet publish`, but it is not needed by the repo's
# `dotnet build` so we keep the CI install minimal.

if [[ -n "${DOTNET_INSTALL_DIR:-}" ]]; then
dotnet_root="$DOTNET_INSTALL_DIR"
else
dotnet_root="${repo_root}.dotnet"
fi

dotnet_exe="$dotnet_root/dotnet"

if [[ ! -x "$dotnet_exe" ]]; then
Write-PipelineTelemetryError -category 'InitializeToolset' "restore-toolset.sh: dotnet executable not found at '$dotnet_exe'."
ExitWithExitCode 1
fi

# Cheap, network-free probe: parse `dotnet workload list` text to skip already-installed workloads.
list_output=$("$dotnet_exe" workload list 2>&1)
missing=()
for workload in "${required_workloads[@]}"; do
if ! grep -qE "^[[:space:]]*${workload}[[:space:]]" <<<"$list_output"; then
missing+=("$workload")
fi
done

if [[ ${#missing[@]} -eq 0 ]]; then
echo "All required workloads already installed: ${required_workloads[*]}"
return 0
fi

echo "Installing .NET SDK workloads: ${missing[*]}"
"$dotnet_exe" workload install "${missing[@]}"
workload_install_exit_code=$?
if [[ $workload_install_exit_code -ne 0 ]]; then
Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to install workloads '${missing[*]}' (dotnet workload install exit code $workload_install_exit_code)."
ExitWithExitCode $workload_install_exit_code
fi
3 changes: 2 additions & 1 deletion samples/WasiPlayground/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
testApplicationBuilder.AddTrxReportProvider();
testApplicationBuilder.AddAppInsightsTelemetryProvider();
testApplicationBuilder.AddCrashDumpProvider();
testApplicationBuilder.AddHangDumpProvider();
// AddHangDumpProvider is intentionally not registered: hang dumps rely on
// System.Diagnostics.Process which is unsupported on wasi (see #8557).
testApplicationBuilder.AddAzureDevOpsProvider();
using ITestApplication testApplication = await testApplicationBuilder.BuildAsync();
return await testApplication.RunAsync();
Expand Down
82 changes: 73 additions & 9 deletions samples/WasiPlayground/README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,85 @@
# WasiPlayground

To run this project:
Demonstrates hosting [Microsoft.Testing.Platform](https://aka.ms/testingplatform/) on the WebAssembly System Interface (WASI).

1. Run `dotnet workload install wasi-experimental`
2. Run `dotnet build`
3. Install wasmtime. See docs at <https://docs.wasmtime.dev/cli-install.html>.
4. Open command-line in AppBundle directory (`artifacts\bin\WasiPlayground\Debug\net10.0\wasi-wasm\AppBundle`)
5. Run `wasmtime run --wasi http --dir . -- dotnet.wasm WasiPlayground`
## Build & run

Prerequisites:

- The repo-local .NET SDK at `.dotnet\dotnet.exe`. Bootstrap it once by running
`.\build.cmd` (Windows) or `./build.sh` (Linux/macOS) from the repo root;
this also installs the `wasi-experimental-net10` workload required to build
the sample (see [`eng/restore-toolset.ps1`](../../eng/restore-toolset.ps1)
/ [`eng/restore-toolset.sh`](../../eng/restore-toolset.sh)).
- The `wasm-tools-net10` workload, which is only needed for `dotnet publish`
(not by the repo's `dotnet build`), so install it manually:

```cmd
.\.dotnet\dotnet.exe workload install wasm-tools-net10
```

- [wasmtime](https://docs.wasmtime.dev/cli-install.html) on `PATH`.

> All commands below use `.\.dotnet\dotnet.exe` so that the `.dotnet\packs`
> lookup in step 2 resolves. If you prefer a machine-installed SDK, swap
> `dotnet` in and replace `.dotnet\packs` with the corresponding `packs`
> folder reported by `dotnet --info` (look for *.NET SDKs installed*).

Then:

1. From the repo root, publish the sample:

```cmd
.\.dotnet\dotnet.exe publish samples\WasiPlayground\WasiPlayground.csproj -c Debug -f net10.0
```

2. The pre-built `dotnet.wasm` does not embed the ICU data file, so copy it
next to the bundle. First list the installed runtime-pack version (a
single folder name such as `10.0.8`), then substitute it into the copy
command:

```cmd
dir /b .dotnet\packs\Microsoft.NETCore.App.Runtime.Mono.wasi-wasm

copy .dotnet\packs\Microsoft.NETCore.App.Runtime.Mono.wasi-wasm\<runtime-version>\runtimes\wasi-wasm\native\icudt.dat ^
artifacts\bin\WasiPlayground\Debug\net10.0\wasi-wasm\AppBundle\
```

Comment thread
Evangelink marked this conversation as resolved.
3. Switch to the bundle directory and invoke `wasmtime` (the `-S http` flag is required because the runtime imports `wasi:http`):

```cmd
cd artifacts\bin\WasiPlayground\Debug\net10.0\wasi-wasm\AppBundle
wasmtime run -S http --dir . -- dotnet.wasm WasiPlayground
```

## Status

As of today, this will produce this exception (it's not yet supported by MTP).
After publishing, Microsoft.Testing.Platform now boots on `wasi-wasm` (the
banner reads `[wasi-wasm - net10.0]`) thanks to [#7137](https://github.com/microsoft/testfx/pull/7137).
Test execution itself currently fails with:

```console
Microsoft.Testing.Platform v2.3.0-dev (UTC ...) [wasi-wasm - net10.0]
...
Unhandled Exception:
System.PlatformNotSupportedException: Arg_PlatformNotSupported
at System.Threading.Tasks.Task.InternalWaitCore(Int32 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.Tasks.Task.InternalWait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.Tasks.Task.InternalWaitCore(...)
at System.Threading.Tasks.Task.InternalWait(...)
at Program.<Main>(String[] args)
```

The exception comes from the C# compiler's synthetic `Main` wrapper for
`async Task Main`, which calls `Task.GetAwaiter().GetResult()` &rarr;
`Task.Wait()`. On single-threaded `wasi-wasm` (no thread pool) this throws
`PlatformNotSupportedException`. Tracked in [#5366](https://github.com/microsoft/testfx/issues/5366)
so this sample can act as the canonical repro.

## Build configuration notes

`WasiPlayground.csproj` ships with a few non-obvious switches:

| Property | Reason |
| --- | --- |
| `UsingWasiRuntimeWorkload=true` | Workaround for an SDK manifest bug in `11.0.100-preview.5` where `$(UsingWasiRuntimeWorkload)` never resolves to `true` for net10.0 projects, so the WASI Sdk targets are never imported and no `dotnet.wasm` is produced. |
| `WasmSingleFileBundle=false` | Single-file bundling requires the [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) (clang) toolchain to relink the native runtime. Keeping the managed assemblies on disk avoids that requirement. |
| `InvariantGlobalization=true` | Prevents the trimmer from crashing during publish, but the pre-built `dotnet.wasm` still loads ICU at runtime, so `icudt.dat` must still be staged next to the bundle (see step 2). |
23 changes: 21 additions & 2 deletions samples/WasiPlayground/WasiPlayground.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,29 @@
<TargetFrameworks>net10.0</TargetFrameworks>
<RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
<PublishTrimmed>true</PublishTrimmed>
<!-- Use the pre-built dotnet.wasm and load managed assemblies from disk (no wasi-sdk required) -->
<WasmSingleFileBundle>false</WasmSingleFileBundle>
<InvariantGlobalization>true</InvariantGlobalization>

<!--
Workaround for a bug in 11.0.100-preview.5 SDK manifests where
$(UsingWasiRuntimeWorkload) never resolves to 'true' for net10.0 projects,
so the WASI Sdk targets (Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk.net10)
are never imported and no dotnet.wasm bundle is produced.
See: WorkloadManifest.Wasi.targets only checks $(WasiNativeWorkload10) when
$(TargetsCurrent) (net11.0) is true, never when $(TargetsNet10) is true.
-->
<UsingWasiRuntimeWorkload>true</UsingWasiRuntimeWorkload>

<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
<IsTestingPlatformApplication>true</IsTestingPlatformApplication>
<!--
IsTestingPlatformApplication is intentionally NOT set when running under
'dotnet test' (CI's Debug Test step on TestFx.slnx passes -p:UsingDotNetTest=true).
The wasi-wasm executable can only run via WasmAppHost + wasmtime, which is not
available on Windows CI agents. Keeping the property true during a normal build
ensures the MTP entry point is still generated and baked into the wasm bundle.
-->
<IsTestingPlatformApplication Condition=" '$(UsingDotNetTest)' != 'true' ">true</IsTestingPlatformApplication>
</PropertyGroup>

<ItemGroup>
Expand Down
Loading