diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets
index 1c5cb833874880..3266e12f988d02 100644
--- a/src/mono/wasm/build/WasmApp.targets
+++ b/src/mono/wasm/build/WasmApp.targets
@@ -113,20 +113,34 @@
-1
+
+
+
+
+ <_AppBundleDirForRunCommand Condition="'$(WasmAppDir)' != ''">$(WasmAppDir)
+
+
- <_AppBundleDirForRunCommand Condition="Exists('$(WasmAppDir)/$(AssemblyName).runtimeconfig.json')">$(WasmAppDir)
- <_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' == '' and Exists('$(OutputPath)/AppBundle')">$([MSBuild]::NormalizeDirectory($(OutputPath), 'AppBundle'))
- <_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' == '' and Exists('$(OutputPath)/browser-wasm/AppBundle')">$([MSBuild]::NormalizeDirectory($(OutputPath), 'browser-wasm', 'AppBundle'))
- <_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' == ''">OutputPath=$(OutputPath), OutDir=$(OutDir)
+ The path might not have been created yet, for example when creating a new project in VS, so don't use an Exists() check
+ -->
+ <_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' == ''">$([System.IO.Path]::Combine($(OutputPath), 'browser-wasm', 'AppBundle'))
+
+
+ <_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' != '' and !$([System.IO.Path]::IsPathRooted($(_AppBundleDirForRunCommand)))">$([System.IO.Path]::Combine($(MSBuildProjectDirectory), $(_AppBundleDirForRunCommand)))
$(DOTNET_HOST_PATH)
dotnet
- exec "$([MSBuild]::NormalizePath($(WasmAppHostDir), 'WasmAppHost.dll'))" --runtime-config "$(_AppBundleDirForRunCommand)/$(AssemblyName).runtimeconfig.json" $(WasmHostArguments)
+
+ <_RuntimeConfigJsonPath>$([MSBuild]::NormalizePath($(_AppBundleDirForRunCommand), '$(AssemblyName).runtimeconfig.json'))
+ exec "$([MSBuild]::NormalizePath($(WasmAppHostDir), 'WasmAppHost.dll'))" --runtime-config "$(_RuntimeConfigJsonPath)" $(WasmHostArguments)
$(_AppBundleDirForRunCommand)
diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmTemplateTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmTemplateTests.cs
index 08fc1180c849bd..285e1b665752c0 100644
--- a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmTemplateTests.cs
+++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmTemplateTests.cs
@@ -21,7 +21,7 @@ public WasmTemplateTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur
{
}
- private void updateProgramCS()
+ private void UpdateProgramCS()
{
string programText = """
Console.WriteLine("Hello, Console!");
@@ -188,7 +188,7 @@ public void ConsoleBuildAndRun(string config, bool relinking)
string projectFile = CreateWasmTemplateProject(id, "wasmconsole");
string projectName = Path.GetFileNameWithoutExtension(projectFile);
- updateProgramCS();
+ UpdateProgramCS();
UpdateConsoleMainJs();
if (relinking)
AddItemsPropertiesToProject(projectFile, "true");
@@ -216,6 +216,112 @@ public void ConsoleBuildAndRun(string config, bool relinking)
Assert.Contains("args[2] = z", output);
}
+ public static TheoryData TestDataForAppBundleDir()
+ {
+ var data = new TheoryData();
+ AddTestData(forConsole: true, runOutsideProjectDirectory: false);
+ AddTestData(forConsole: true, runOutsideProjectDirectory: true);
+
+ AddTestData(forConsole: false, runOutsideProjectDirectory: false);
+ AddTestData(forConsole: false, runOutsideProjectDirectory: true);
+
+ void AddTestData(bool forConsole, bool runOutsideProjectDirectory)
+ {
+ data.Add(runOutsideProjectDirectory, forConsole, string.Empty);
+
+ data.Add(runOutsideProjectDirectory, forConsole,
+ $"{Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())}");
+ data.Add(runOutsideProjectDirectory, forConsole,
+ $"{Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())}");
+ }
+
+ return data;
+ }
+
+ [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
+ [MemberData(nameof(TestDataForAppBundleDir))]
+ public async Task RunWithDifferentAppBundleLocations(bool forConsole, bool runOutsideProjectDirectory, string extraProperties)
+ => await (forConsole
+ ? ConsoleRunWithAndThenWithoutBuildAsync("Release", extraProperties, runOutsideProjectDirectory)
+ : BrowserRunTwiceWithAndThenWithoutBuildAsync("Release", extraProperties, runOutsideProjectDirectory));
+
+ private async Task BrowserRunTwiceWithAndThenWithoutBuildAsync(string config, string extraProperties = "", bool runOutsideProjectDirectory = false)
+ {
+ string id = $"browser_{config}_{Path.GetRandomFileName()}";
+ string projectFile = CreateWasmTemplateProject(id, "wasmbrowser");
+
+ UpdateBrowserMainJs();
+
+ if (!string.IsNullOrEmpty(extraProperties))
+ AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties);
+
+ string workingDir = runOutsideProjectDirectory ? Path.GetTempPath() : _projectDir!;
+
+ {
+ using var runCommand = new RunCommand(s_buildEnv, _testOutput)
+ .WithWorkingDirectory(workingDir);
+
+ await using var runner = new BrowserRunner();
+ 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();
+ 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));
+ }
+ }
+
+ private Task ConsoleRunWithAndThenWithoutBuildAsync(string config, string extraProperties = "", bool runOutsideProjectDirectory = false)
+ {
+ string id = $"console_{config}_{Path.GetRandomFileName()}";
+ string projectFile = CreateWasmTemplateProject(id, "wasmconsole");
+
+ UpdateProgramCS();
+ UpdateConsoleMainJs();
+
+ if (!string.IsNullOrEmpty(extraProperties))
+ AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties);
+
+ string workingDir = runOutsideProjectDirectory ? Path.GetTempPath() : _projectDir!;
+
+ {
+ string runArgs = $"run -c {config} --project {projectFile}";
+ runArgs += " x y z";
+ using var cmd = new RunCommand(s_buildEnv, _testOutput, label: id)
+ .WithWorkingDirectory(workingDir)
+ .WithEnvironmentVariables(s_buildEnv.EnvVars);
+ var res = cmd.ExecuteWithCapturedOutput(runArgs).EnsureExitCode(42);
+
+ Assert.Contains("args[0] = x", res.Output);
+ Assert.Contains("args[1] = y", res.Output);
+ Assert.Contains("args[2] = z", res.Output);
+ }
+
+ _testOutput.WriteLine($"{Environment.NewLine}[{id}] Running again with --no-build{Environment.NewLine}");
+
+ {
+ // Run with --no-build
+ string runArgs = $"run -c {config} --project {projectFile} --no-build";
+ runArgs += " x y z";
+ using var cmd = new RunCommand(s_buildEnv, _testOutput, label: id)
+ .WithWorkingDirectory(workingDir);
+ var res = cmd.ExecuteWithCapturedOutput(runArgs).EnsureExitCode(42);
+
+ Assert.Contains("args[0] = x", res.Output);
+ Assert.Contains("args[1] = y", res.Output);
+ Assert.Contains("args[2] = z", res.Output);
+ }
+
+ return Task.CompletedTask;
+ }
+
public static TheoryData TestDataForConsolePublishAndRun()
{
var data = new TheoryData();
@@ -242,7 +348,7 @@ public void ConsolePublishAndRun(string config, bool aot, bool relinking)
string projectFile = CreateWasmTemplateProject(id, "wasmconsole");
string projectName = Path.GetFileNameWithoutExtension(projectFile);
- updateProgramCS();
+ UpdateProgramCS();
UpdateConsoleMainJs();
if (aot)