Skip to content

Commit

Permalink
[release/7.0] Fix running WebAssembly Browser App from VS (#75508)
Browse files Browse the repository at this point in the history
* trying to fix 75356

* Trying to fix for running from VS and from command line

* Remove unused spaces

* Fix again

* Addressing @radical comments

* OutputPath can be missing the rid during the evaluation

* more cleanup

* Replacing NormalizeDirectory with [System.IO.Path]::Combine

* [wasm] Add tests for running templates outside project directory

* Add tests for browser template

* fix

Co-authored-by: Thays Grazia <thaystg@gmail.com>
Co-authored-by: Ankit Jain <radical@gmail.com>
  • Loading branch information
3 people committed Sep 13, 2022
1 parent 77295ad commit 79e022f
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 11 deletions.
30 changes: 22 additions & 8 deletions src/mono/wasm/build/WasmApp.targets
Original file line number Diff line number Diff line change
Expand Up @@ -113,20 +113,34 @@

<!-- if DebuggerSupport==true, then ensure that WasmDebugLevel isn't disabling debugging -->
<WasmDebugLevel Condition="('$(WasmDebugLevel)' == '' or '$(WasmDebugLevel)' == '0') and ('$(DebuggerSupport)' == 'true' or '$(Configuration)' == 'Debug')">-1</WasmDebugLevel>
</PropertyGroup>

<PropertyGroup Label="Identify app bundle directory to run from">
<!-- Allow running from custom WasmAppDir -->
<_AppBundleDirForRunCommand Condition="'$(WasmAppDir)' != ''">$(WasmAppDir)</_AppBundleDirForRunCommand>

<!--
This is the default path. We have to build it explicitly because
RuntimeIdentifierInference.targets is imported after this file, and
updates OutputPath to include the RID. So, we don't have the correct
final OutputPath here. But we need it for `dotnet run` to work, as it
just reads the RunCommand after evaluation.
<!-- workaround: RuntimeIdentifierInference.targets is imported after this file, and updates OutputPath to include
the RID. So, we don't have the correct final OutputPath here. But we need it for `dotnet run` to work,
as it just reads the RunCommand after evaluation. -->
<_AppBundleDirForRunCommand Condition="Exists('$(WasmAppDir)/$(AssemblyName).runtimeconfig.json')">$(WasmAppDir)</_AppBundleDirForRunCommand>
<_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' == '' and Exists('$(OutputPath)/AppBundle')">$([MSBuild]::NormalizeDirectory($(OutputPath), 'AppBundle'))</_AppBundleDirForRunCommand>
<_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' == '' and Exists('$(OutputPath)/browser-wasm/AppBundle')">$([MSBuild]::NormalizeDirectory($(OutputPath), 'browser-wasm', 'AppBundle'))</_AppBundleDirForRunCommand>
<_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' == ''">OutputPath=$(OutputPath), OutDir=$(OutDir)</_AppBundleDirForRunCommand>
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>

<!-- Ensure the path is absolute. In case of VS, the cwd might not be the correct one, so explicitly
use $(MSBuildProjectDirectory). -->
<_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' != '' and !$([System.IO.Path]::IsPathRooted($(_AppBundleDirForRunCommand)))">$([System.IO.Path]::Combine($(MSBuildProjectDirectory), $(_AppBundleDirForRunCommand)))</_AppBundleDirForRunCommand>
</PropertyGroup>

<PropertyGroup Condition="'$(WasmGenerateAppBundle)' == 'true'">
<RunCommand Condition="'$(DOTNET_HOST_PATH)' != '' and Exists($(DOTNET_HOST_PATH))">$(DOTNET_HOST_PATH)</RunCommand>
<RunCommand Condition="'$(RunCommand)' == ''">dotnet</RunCommand>
<RunArguments Condition="'$(RunArguments)' == ''">exec &quot;$([MSBuild]::NormalizePath($(WasmAppHostDir), 'WasmAppHost.dll'))&quot; --runtime-config &quot;$(_AppBundleDirForRunCommand)/$(AssemblyName).runtimeconfig.json&quot; $(WasmHostArguments)</RunArguments>

<_RuntimeConfigJsonPath>$([MSBuild]::NormalizePath($(_AppBundleDirForRunCommand), '$(AssemblyName).runtimeconfig.json'))</_RuntimeConfigJsonPath>
<RunArguments Condition="'$(RunArguments)' == ''">exec &quot;$([MSBuild]::NormalizePath($(WasmAppHostDir), 'WasmAppHost.dll'))&quot; --runtime-config &quot;$(_RuntimeConfigJsonPath)&quot; $(WasmHostArguments)</RunArguments>
<RunWorkingDirectory Condition="'$(RunWorkingDirectory)' == ''">$(_AppBundleDirForRunCommand)</RunWorkingDirectory>
</PropertyGroup>

Expand Down
112 changes: 109 additions & 3 deletions src/tests/BuildWasmApps/Wasm.Build.Tests/WasmTemplateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public WasmTemplateTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur
{
}

private void updateProgramCS()
private void UpdateProgramCS()
{
string programText = """
Console.WriteLine("Hello, Console!");
Expand Down Expand Up @@ -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, "<WasmBuildNative>true</WasmBuildNative>");
Expand Down Expand Up @@ -216,6 +216,112 @@ public void ConsoleBuildAndRun(string config, bool relinking)
Assert.Contains("args[2] = z", output);
}

public static TheoryData<bool, bool, string> TestDataForAppBundleDir()
{
var data = new TheoryData<bool, bool, string>();
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,
$"<OutputPath>{Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())}</OutputPath>");
data.Add(runOutsideProjectDirectory, forConsole,
$"<WasmAppDir>{Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())}</WasmAppDir>");
}

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<string, bool, bool> TestDataForConsolePublishAndRun()
{
var data = new TheoryData<string, bool, bool>();
Expand All @@ -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)
Expand Down

0 comments on commit 79e022f

Please sign in to comment.