<SdkBandVersion>8.0.100</SdkBandVersion>
<PackageVersionNet6>6.0.10</PackageVersionNet6>
<!-- problematic until NetCoreAppCurrent is net8.0 <PackageVersionNet7>7.0.0</PackageVersionNet7> -->
+ <PackageVersionForTemplates7>7.0.0-rtm.22476.8</PackageVersionForTemplates7>
<PreReleaseVersionLabel>alpha</PreReleaseVersionLabel>
<PreReleaseVersionIteration>1</PreReleaseVersionIteration>
<!-- Set assembly version to align with major and minor version,
<_WorkloadManifestValues Include="WorkloadVersion" Value="$(PackageVersion)" />
<_WorkloadManifestValues Include="PackageVersionNet7" Value="$(PackageVersionNet7)" Condition="'$(PackageVersionNet7)' != ''" />
<_WorkloadManifestValues Include="PackageVersionNet7" Value="$(PackageVersion)" Condition="'$(PackageVersionNet7)' == ''" />
+
+ <!-- We need to use a different version for net7 templates, to differentiate from net8 ones -->
+ <_WorkloadManifestValues Include="PackageVersionForTemplates7" Value="$(PackageVersionForTemplates7)" Condition="'$(PackageVersionNet7)' == ''" />
+ <_WorkloadManifestValues Include="PackageVersionForTemplates7" Value="$(PackageVersionNet7)" Condition="'$(PackageVersionNet7)' != ''" />
+
<_WorkloadManifestValues Include="EmscriptenVersion" Value="$(MicrosoftNETRuntimeEmscriptenVersion)" />
<_WorkloadManifestValues Include="NetCoreAppCurrent" Value="$(NetCoreAppCurrent)" />
</ItemGroup>
},
"Microsoft.NET.Runtime.WebAssembly.Templates.net7": {
"kind": "template",
- "version": "${PackageVersionNet7}",
+ "version": "${PackageVersionForTemplates7}",
"alias-to": {
"any": "Microsoft.NET.Runtime.WebAssembly.Templates"
}
$(DOTNET) build $(TOP)/src/mono/wasm/debugger/BrowserDebugHost $(MSBUILD_ARGS)
build-dbg-testsuite:
$(DOTNET) build $(TOP)/src/mono/wasm/debugger/DebuggerTestSuite $(MSBUILD_ARGS)
+build-app-host:
+ $(DOTNET) build $(TOP)/src/mono/wasm/host $(_MSBUILD_WASM_BUILD_ARGS) $(MSBUILD_ARGS)
patch-deterministic:
cd emsdk/upstream/emscripten/ && patch -p1 < ../../../runtime/deterministic.diff
using System.Threading.Tasks;
using Microsoft.Playwright;
using Wasm.Tests.Internal;
+using Xunit.Abstractions;
namespace Wasm.Build.Tests;
public Task<CommandResult>? RunTask { get; private set; }
public IList<string> OutputLines { get; private set; } = new List<string>();
private TaskCompletionSource<int> _exited = new();
+ private readonly ITestOutputHelper _testOutput;
+
+ public BrowserRunner(ITestOutputHelper testOutput) => _testOutput = testOutput;
// FIXME: options
public async Task<IPage> RunAsync(ToolCommand cmd, string args, bool headless = true)
var url = new Uri(urlAvailable.Task.Result);
Playwright = await Microsoft.Playwright.Playwright.CreateAsync();
string[] chromeArgs = new[] { $"--explicitly-allowed-ports={url.Port}" };
- Console.WriteLine($"Launching chrome ('{s_chromePath.Value}') via playwright with args = {string.Join(',', chromeArgs)}");
+ _testOutput.WriteLine($"Launching chrome ('{s_chromePath.Value}') via playwright with args = {string.Join(',', chromeArgs)}");
Browser = await Playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions{
ExecutablePath = s_chromePath.Value,
Headless = headless,
await Task.WhenAny(RunTask!, _exited.Task, Task.Delay(timeout));
if (_exited.Task.IsCompleted)
{
- Console.WriteLine ($"Exited with {await _exited.Task}");
+ _testOutput.WriteLine ($"Exited with {await _exited.Task}");
return;
}
await Task.WhenAny(RunTask!, _exited.Task, Task.Delay(timeout));
if (RunTask.IsCanceled)
{
- Console.WriteLine ($"Exited with {(await RunTask).ExitCode}");
+ _testOutput.WriteLine ($"Exited with {(await RunTask).ExitCode}");
return;
}
public static readonly string RelativeTestAssetsPath = @"..\testassets\";
public static readonly string TestAssetsPath = Path.Combine(AppContext.BaseDirectory, "testassets");
public static readonly string TestDataPath = Path.Combine(AppContext.BaseDirectory, "data");
+ public static readonly string TmpPath = Path.Combine(Path.GetTempPath(), "wasmbuildtests");
private static readonly Dictionary<string, string> s_runtimePackVersions = new();
{
LogRootPath = Environment.CurrentDirectory;
}
+
+ if (Directory.Exists(TmpPath))
+ Directory.Delete(TmpPath, recursive: true);
+ Directory.CreateDirectory(TmpPath);
}
// FIXME: error checks
Directory.CreateDirectory(_logPath);
}
- protected static void InitProjectDir(string dir)
+ protected static void InitProjectDir(string dir, bool addNuGetSourceForLocalPackages = false)
{
Directory.CreateDirectory(dir);
File.WriteAllText(Path.Combine(dir, "Directory.Build.props"), s_buildEnv.DirectoryBuildPropsContents);
File.WriteAllText(Path.Combine(dir, "Directory.Build.targets"), s_buildEnv.DirectoryBuildTargetsContents);
- File.Copy(Path.Combine(BuildEnvironment.TestDataPath, NuGetConfigFileNameForDefaultFramework), Path.Combine(dir, "nuget.config"));
+ string targetNuGetConfigPath = Path.Combine(dir, "nuget.config");
+ if (addNuGetSourceForLocalPackages)
+ {
+ File.WriteAllText(targetNuGetConfigPath,
+ GetNuGetConfigWithLocalPackagesPath(
+ Path.Combine(BuildEnvironment.TestDataPath, NuGetConfigFileNameForDefaultFramework),
+ s_buildEnv.BuiltNuGetsPath));
+ }
+ else
+ {
+ File.Copy(Path.Combine(BuildEnvironment.TestDataPath, NuGetConfigFileNameForDefaultFramework),
+ targetNuGetConfigPath);
+ }
Directory.CreateDirectory(Path.Combine(dir, ".nuget"));
}
return contents.Replace(s_nugetInsertionTag, $@"<add key=""nuget-local"" value=""{localNuGetsPath}"" />");
}
- public string CreateWasmTemplateProject(string id, string template = "wasmbrowser")
+ public string CreateWasmTemplateProject(string id, string template = "wasmbrowser", string extraArgs = "")
{
InitPaths(id);
- InitProjectDir(id);
+ InitProjectDir(id, addNuGetSourceForLocalPackages: true);
File.WriteAllText(Path.Combine(_projectDir, "Directory.Build.props"), "<Project />");
File.WriteAllText(Path.Combine(_projectDir, "Directory.Build.targets"),
new DotNetCommand(s_buildEnv, _testOutput, useDefaultArgs: false)
.WithWorkingDirectory(_projectDir!)
- .ExecuteWithCapturedOutput($"new {template}")
+ .ExecuteWithCapturedOutput($"new {template} {extraArgs}")
.EnsureSuccessful();
return Path.Combine(_projectDir!, $"{id}.csproj");
[InlineData("Debug", true)]
[InlineData("Release", false)]
[InlineData("Release", true)]
- public void ConsoleBuildAndRun(string config, bool relinking)
+ public void ConsoleBuildAndRunDefault(string config, bool relinking)
+ => ConsoleBuildAndRun(config, relinking, string.Empty);
+
+ [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
+ [InlineData("Debug", "-f net7.0")]
+ [InlineData("Debug", "-f net8.0")]
+ public void ConsoleBuildAndRunForSpecificTFM(string config, string extraNewArgs)
+ => ConsoleBuildAndRun(config, false, extraNewArgs);
+
+ private void ConsoleBuildAndRun(string config, bool relinking, string extraNewArgs)
{
string id = $"{config}_{Path.GetRandomFileName()}";
- string projectFile = CreateWasmTemplateProject(id, "wasmconsole");
+ string projectFile = CreateWasmTemplateProject(id, "wasmconsole", extraNewArgs);
string projectName = Path.GetFileNameWithoutExtension(projectFile);
UpdateProgramCS();
//data.Add(runOutsideProjectDirectory, forConsole, string.Empty);
data.Add(runOutsideProjectDirectory, forConsole,
- $"<OutputPath>{Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())}</OutputPath>");
+ $"<OutputPath>{Path.Combine(BuildEnvironment.TmpPath, Path.GetRandomFileName())}</OutputPath>");
data.Add(runOutsideProjectDirectory, forConsole,
- $"<WasmAppDir>{Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())}</WasmAppDir>");
+ $"<WasmAppDir>{Path.Combine(BuildEnvironment.TmpPath, Path.GetRandomFileName())}</WasmAppDir>");
}
return data;
if (!string.IsNullOrEmpty(extraProperties))
AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties);
- string workingDir = runOutsideProjectDirectory ? Path.GetTempPath() : _projectDir!;
+ string workingDir = runOutsideProjectDirectory ? BuildEnvironment.TmpPath : _projectDir!;
{
using var runCommand = new RunCommand(s_buildEnv, _testOutput)
.WithWorkingDirectory(workingDir);
- await using var runner = new BrowserRunner();
+ await using var runner = new BrowserRunner(_testOutput);
var page = await runner.RunAsync(runCommand, $"run -c {config} --project {projectFile} --forward-console");
await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2));
Assert.Contains("Hello, Browser!", string.Join(Environment.NewLine, runner.OutputLines));
using var runCommand = new RunCommand(s_buildEnv, _testOutput)
.WithWorkingDirectory(workingDir);
- await using var runner = new BrowserRunner();
+ await using var runner = new BrowserRunner(_testOutput);
var page = await runner.RunAsync(runCommand, $"run -c {config} --no-build --project {projectFile} --forward-console");
await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2));
Assert.Contains("Hello, Browser!", string.Join(Environment.NewLine, runner.OutputLines));
if (!string.IsNullOrEmpty(extraProperties))
AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties);
- string workingDir = runOutsideProjectDirectory ? Path.GetTempPath() : _projectDir!;
+ string workingDir = runOutsideProjectDirectory ? BuildEnvironment.TmpPath : _projectDir!;
{
string runArgs = $"run -c {config} --project {projectFile}";
using var runCommand = new RunCommand(s_buildEnv, _testOutput)
.WithWorkingDirectory(_projectDir!);
- await using var runner = new BrowserRunner();
+ await using var runner = new BrowserRunner(_testOutput);
var page = await runner.RunAsync(runCommand, $"run -c {config} --no-build");
await page.Locator("text=Counter").ClickAsync();
Assert.Equal("Current count: 1", txt);
}
- [ConditionalFact(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
- public async Task BrowserTest()
+ [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
+ [InlineData("")]
+ [InlineData("-f net7.0")]
+ [InlineData("-f net8.0")]
+ public async Task BrowserBuildAndRun(string extraNewArgs)
{
string config = "Debug";
string id = $"browser_{config}_{Path.GetRandomFileName()}";
- CreateWasmTemplateProject(id, "wasmbrowser");
+ CreateWasmTemplateProject(id, "wasmbrowser", extraNewArgs);
UpdateBrowserMainJs(DefaultTargetFramework);
using var runCommand = new RunCommand(s_buildEnv, _testOutput)
.WithWorkingDirectory(_projectDir!);
- await using var runner = new BrowserRunner();
+ await using var runner = new BrowserRunner(_testOutput);
var page = await runner.RunAsync(runCommand, $"run -c {config} --no-build -r browser-wasm --forward-console");
await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2));
Assert.Contains("Hello, Browser!", string.Join(Environment.NewLine, runner.OutputLines));
// 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.IO;
using System.Runtime.ExceptionServices;
debugging: _args.CommonConfig.Debugging);
runArgsJson.Save(Path.Combine(_args.CommonConfig.AppPath, "runArgs.json"));
- var urls = new string[] { $"http://localhost:{_args.CommonConfig.HostProperties.WebServerPort}", "https://localhost:0" };
- if (envVars["ASPNETCORE_URLS"] is not null)
- urls = envVars["ASPNETCORE_URLS"].Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
+ string[] urls = envVars.TryGetValue("ASPNETCORE_URLS", out string? aspnetUrls)
+ ? aspnetUrls.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
+ : new string[] { $"http://127.0.0.1:{_args.CommonConfig.HostProperties.WebServerPort}", "https://127.0.0.1:0" };
(ServerURLs serverURLs, IWebHost host) = await StartWebServerAsync(_args.CommonConfig.AppPath,
_args.ForwardConsoleOutput ?? false,
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
{
internal static async Task<(ServerURLs, IWebHost)> StartAsync(WebServerOptions options, ILogger logger, CancellationToken token)
{
- string[] urls = options.Urls;
+ TaskCompletionSource<ServerURLs> realUrlsAvailableTcs = new();
IWebHostBuilder builder = new WebHostBuilder()
.UseKestrel()
}
services.AddSingleton(logger);
services.AddSingleton(Options.Create(options));
+ services.AddSingleton(realUrlsAvailableTcs);
services.AddRouting();
})
- .UseUrls(urls);
+ .UseUrls(options.Urls);
if (options.ContentRootPath != null)
builder.UseContentRoot(options.ContentRootPath);
IWebHost? host = builder.Build();
await host.StartAsync(token);
- ICollection<string>? addresses = host.ServerFeatures
- .Get<IServerAddressesFeature>()?
- .Addresses;
+ if (token.CanBeCanceled)
+ token.Register(async () => await host.StopAsync());
- string? ipAddress =
- addresses?
- .Where(a => a.StartsWith("http:", StringComparison.InvariantCultureIgnoreCase))
- .Select(a => new Uri(a))
- .Select(uri => uri.ToString())
- .FirstOrDefault();
-
- string? ipAddressSecure =
- addresses?
- .Where(a => a.StartsWith("https:", StringComparison.OrdinalIgnoreCase))
- .Select(a => new Uri(a))
- .Select(uri => uri.ToString())
- .FirstOrDefault();
-
- return ipAddress == null || ipAddressSecure == null
- ? throw new InvalidOperationException("Failed to determine web server's IP address or port")
- : (new ServerURLs(ipAddress, ipAddressSecure), host);
+ ServerURLs serverUrls = await realUrlsAvailableTcs.Task;
+ return (serverUrls, host);
}
}
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
-using System.Net;
+using System.Linq;
using System.Net.WebSockets;
-using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-using Microsoft.WebAssembly.Diagnostics;
#nullable enable
private readonly IWebHostEnvironment _hostingEnvironment;
private static readonly object LaunchLock = new object();
private static string LaunchedDebugProxyUrl = "";
+ private ILogger? _logger;
public WebServerStartup(IWebHostEnvironment hostingEnvironment) => _hostingEnvironment = hostingEnvironment;
public static int StartDebugProxy(string devToolsHost)
return generateRandomPort;
}
- public void Configure(IApplicationBuilder app, IOptions<WebServerOptions> optionsContainer)
+ public void Configure(IApplicationBuilder app,
+ IOptions<WebServerOptions> optionsContainer,
+ TaskCompletionSource<ServerURLs> realUrlsAvailableTcs,
+ ILogger logger,
+ IHostApplicationLifetime applicationLifetime)
{
+ _logger = logger;
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".wasm"] = "application/wasm";
provider.Mappings[".cjs"] = "text/javascript";
return Task.CompletedTask;
});
});
+
+
+ applicationLifetime.ApplicationStarted.Register(() =>
+ {
+ TaskCompletionSource<ServerURLs> tcs = realUrlsAvailableTcs;
+ try
+ {
+ ICollection<string>? addresses = app.ServerFeatures
+ .Get<IServerAddressesFeature>()
+ ?.Addresses;
+
+ string? ipAddress = null;
+ string? ipAddressSecure = null;
+ if (addresses is not null)
+ {
+ ipAddress = GetHttpServerAddress(addresses, secure: false);
+ ipAddressSecure = GetHttpServerAddress(addresses, secure: true);
+ }
+
+ if (ipAddress == null)
+ tcs.SetException(new InvalidOperationException("Failed to determine web server's IP address or port"));
+ else
+ tcs.SetResult(new ServerURLs(ipAddress, ipAddressSecure));
+ }
+ catch (Exception ex)
+ {
+ _logger?.LogError($"Failed to get urls for the webserver: {ex}");
+ tcs.TrySetException(ex);
+ throw;
+ }
+
+ static string? GetHttpServerAddress(ICollection<string> addresses, bool secure)
+ => addresses?
+ .Where(a => a.StartsWith(secure ? "https:" : "http:", StringComparison.InvariantCultureIgnoreCase))
+ .Select(a => new Uri(a))
+ .Select(uri => uri.ToString())
+ .FirstOrDefault();
+ });
}
}
"author": "Microsoft",
"classifications": [ "Web", "WebAssembly", "Browser" ],
"generatorVersions": "[1.0.0.0-*)",
- "identity": "WebAssembly.Browser",
+ "groupIdentity": "WebAssembly.Browser",
+ "precedence": 8000,
+ "identity": "WebAssembly.Browser.8.0",
+ "description": "WebAssembly Browser App",
"name": "WebAssembly Browser App",
"description": "A project template for creating a .NET app that runs on WebAssembly in a browser",
"shortName": "wasmbrowser",
"low": 5000,
"high": 5300
},
- "replaces": "5000"
+ "replaces": "5000"
},
"kestrelHttpsPortGenerated": {
"type": "generated",
"low": 7000,
"high": 7300
},
- "replaces": "5001"
+ "replaces": "5001"
+ },
+ "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",
+ "displayName": "framework"
}
}
}
"$schema": "http://json.schemastore.org/template",
"author": "Microsoft",
"classifications": [ "Web", "WebAssembly", "Console" ],
- "identity": "WebAssembly.Console",
+ "groupIdentity": "WebAssembly.Console",
+ "precedence": 8000,
+ "identity": "WebAssembly.Console.8.0",
+ "description": "WebAssembly Console App",
"name": "WebAssembly Console App",
"description": "A project template for creating a .NET app that runs on WebAssembly on Node JS or V8",
"shortName": "wasmconsole",
"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",
+ "displayName": "framework"
+ }
}
}
}
string cachePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
+ string lastTargetPath = string.Empty;
foreach (InstallWorkloadRequest req in selectedRequests)
{
- Log.LogMessage(MessageImportance.High, $"** Installing workload {req.WorkloadId} in {req.TargetPath} **");
+ if (req.TargetPath != lastTargetPath)
+ Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** Preparing {req.TargetPath} **");
+ lastTargetPath = req.TargetPath;
+
+ Log.LogMessage(MessageImportance.High, $" - {req.WorkloadId}: Installing workload");
if (!req.Validate(Log))
return false;
if (manifestsInstalled.Contains(req.ManifestName))
{
- Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** Manifests for workload {req.WorkloadId} are already installed **{Environment.NewLine}");
+ Log.LogMessage(MessageImportance.High, $"** {req.WorkloadId}: Manifests are already installed **");
continue;
}
- Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** Installing manifests for workload {req.WorkloadId} **");
+ Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** {req.WorkloadId}: Installing manifests **");
if (!InstallWorkloadManifest(workload,
req.ManifestName,
req.Version,
Path.Combine(req.TargetPath, "dotnet"),
$"workload install --skip-manifest-update --no-cache --configfile \"{nugetConfigPath}\" {req.WorkloadId}",
workingDir: Path.GetTempPath(),
- silent: false,
logStdErrAsMessage: req.IgnoreErrors,
- debugMessageImportance: MessageImportance.High);
+ debugMessageImportance: MessageImportance.Normal);
if (exitCode != 0)
{
if (req.IgnoreErrors)
{
+ Log.LogMessage(MessageImportance.High, output);
Log.LogMessage(MessageImportance.High,
$"{Environment.NewLine} ** Ignoring workload installation failure exit code {exitCode}. **{Environment.NewLine}");
}
Log.LogError($"workload install failed with exit code {exitCode}: {output}");
}
+ Log.LogMessage(MessageImportance.Low, $"List of the relevant paths in {req.TargetPath}");
foreach (string dir in Directory.EnumerateDirectories(Path.Combine(req.TargetPath, "sdk-manifests"), "*", SearchOption.AllDirectories))
Log.LogMessage(MessageImportance.Low, $"\t{Path.Combine(req.TargetPath, "sdk-manifests", dir)}");
private void UpdateAppRef(string sdkPath, string version)
{
- Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** Updating Targeting pack **{Environment.NewLine}");
+ Log.LogMessage(MessageImportance.Normal, $" - Updating Targeting pack");
string pkgPath = Path.Combine(LocalNuGetsPath, $"Microsoft.NETCore.App.Ref.{version}.nupkg");
if (!File.Exists(pkgPath))
private bool InstallWorkloadManifest(ITaskItem workloadId, string name, string version, string sdkDir, string nugetConfigContents, bool stopOnMissing)
{
- Log.LogMessage(MessageImportance.High, $" ** Installing manifest: {name}/{version}");
+ Log.LogMessage(MessageImportance.High, $" - Installing manifest: {name}/{version}");
// Find any existing directory with the manifest name, ignoring the case
// Multiple directories for a manifest, differing only in case causes