diff --git a/eng/testing/scenarios/BuildWasmAppsJobsList.txt b/eng/testing/scenarios/BuildWasmAppsJobsList.txt index 11bfdab2c7414e..58c5621f452401 100644 --- a/eng/testing/scenarios/BuildWasmAppsJobsList.txt +++ b/eng/testing/scenarios/BuildWasmAppsJobsList.txt @@ -46,4 +46,5 @@ Wasm.Build.Tests.WasmSIMDTests Wasm.Build.Tests.WasmTemplateTests Wasm.Build.Tests.WorkloadTests Wasm.Build.Tests.MT.Blazor.SimpleMultiThreadedTests -Wasm.Build.Tests.TestAppScenarios.DebugLevelTests +Wasm.Build.Tests.TestAppScenarios.WasmSdkDebugLevelTests +Wasm.Build.Tests.TestAppScenarios.WasmAppBuilderDebugLevelTests diff --git a/src/mono/browser/build/BrowserWasmApp.targets b/src/mono/browser/build/BrowserWasmApp.targets index bd5a3b149716f1..fc8708fed161d8 100644 --- a/src/mono/browser/build/BrowserWasmApp.targets +++ b/src/mono/browser/build/BrowserWasmApp.targets @@ -127,6 +127,7 @@ <_WasmPThreadPoolInitialSize Condition="'$(_WasmPThreadPoolInitialSize)' == ''">-1 <_WasmPThreadPoolUnusedSize Condition="'$(_WasmPThreadPoolUnusedSize)' == ''">-1 + <_WasmIsPublishing Condition="'$(_WasmIsPublishing)' == '' and '$(_IsPublishing)' != ''">$(_IsPublishing) @@ -152,6 +153,7 @@ ExtraConfig="@(WasmExtraConfig)" NativeAssets="@(WasmNativeAsset)" DebugLevel="$(WasmDebugLevel)" + IsPublish="$(_WasmIsPublishing)" IncludeThreadsWorker="$(WasmEnableThreads)" PThreadPoolInitialSize="$(_WasmPThreadPoolInitialSize)" PThreadPoolUnusedSize="$(_WasmPThreadPoolUnusedSize)" diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DebugLevelTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DebugLevelTests.cs deleted file mode 100644 index ee69b34819bbdc..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DebugLevelTests.cs +++ /dev/null @@ -1,109 +0,0 @@ -// 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.Linq; -using System.Threading.Tasks; -using Xunit; -using Xunit.Abstractions; - -#nullable enable - -namespace Wasm.Build.Tests.TestAppScenarios; - -public class DebugLevelTests : AppTestBase -{ - public DebugLevelTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - } - - private void AssertDebugLevel(RunResult result, int value) - { - Assert.Collection( - result.TestOutput, - m => Assert.Equal($"WasmDebugLevel: {value}", m) - ); - } - - [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task BuildWithDefaultLevel(string configuration) - { - CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_BuildWithDefaultLevel_{configuration}", "App"); - BuildProject(configuration); - - var result = await RunSdkStyleAppForBuild(new( - Configuration: configuration, - TestScenario: "DebugLevelTest" - )); - AssertDebugLevel(result, -1); - } - - [Theory] - [InlineData("Debug", 1)] - [InlineData("Release", 1)] - [InlineData("Debug", 0)] - [InlineData("Release", 0)] - public async Task BuildWithExplicitValue(string configuration, int debugLevel) - { - CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_BuildWithExplicitValue_{configuration}", "App"); - BuildProject(configuration: configuration, extraArgs: $"-p:WasmDebugLevel={debugLevel}"); - - var result = await RunSdkStyleAppForBuild(new( - Configuration: configuration, - TestScenario: "DebugLevelTest" - )); - AssertDebugLevel(result, debugLevel); - } - - [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task PublishWithDefaultLevel(string configuration) - { - CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_PublishWithDefaultLevel_{configuration}", "App"); - PublishProject(configuration); - - var result = await RunSdkStyleAppForPublish(new( - Configuration: configuration, - TestScenario: "DebugLevelTest" - )); - AssertDebugLevel(result, 0); - } - - [Theory] - [InlineData("Debug", 1)] - [InlineData("Release", 1)] - [InlineData("Debug", -1)] - [InlineData("Release", -1)] - public async Task PublishWithExplicitValue(string configuration, int debugLevel) - { - CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_PublishWithExplicitValue_{configuration}", "App"); - PublishProject(configuration, RuntimeVariant.SingleThreaded, assertAppBundle: true, $"-p:WasmDebugLevel={debugLevel}"); - - var result = await RunSdkStyleAppForPublish(new( - Configuration: configuration, - TestScenario: "DebugLevelTest" - )); - AssertDebugLevel(result, debugLevel); - } - - [Theory] - [InlineData("Debug")] - [InlineData("Release")] - public async Task PublishWithDefaultLevelAndPdbs(string configuration) - { - CopyTestAsset("WasmBasicTestApp", $"DebugLevelTests_PublishWithDefaultLevelAndPdbs_{configuration}", "App"); - PublishProject(configuration, RuntimeVariant.SingleThreaded, assertAppBundle: true, $"-p:CopyOutputSymbolsToPublishDirectory=true"); - - var result = await RunSdkStyleAppForPublish(new( - Configuration: configuration, - TestScenario: "DebugLevelTest" - )); - AssertDebugLevel(result, -1); - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DebugLevelTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DebugLevelTestsBase.cs new file mode 100644 index 00000000000000..cac95e26fa3f20 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/DebugLevelTestsBase.cs @@ -0,0 +1,86 @@ +// 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.Linq; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests.TestAppScenarios; + +public abstract class DebugLevelTestsBase : AppTestBase +{ + public DebugLevelTestsBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + protected void AssertDebugLevel(RunResult result, int value) + { + Assert.Collection( + result.TestOutput, + m => Assert.Equal($"WasmDebugLevel: {value}", m) + ); + } + + protected abstract void SetupProject(string projectId); + protected abstract Task RunForBuild(string configuration); + protected abstract Task RunForPublish(string configuration); + + [Theory] + [InlineData("Debug")] + [InlineData("Release")] + public async Task BuildWithDefaultLevel(string configuration) + { + SetupProject($"DebugLevelTests_BuildWithDefaultLevel_{configuration}"); + BuildProject(configuration, assertAppBundle: false); + + var result = await RunForBuild(configuration); + AssertDebugLevel(result, -1); + } + + [Theory] + [InlineData("Debug", 1)] + [InlineData("Release", 1)] + [InlineData("Debug", 0)] + [InlineData("Release", 0)] + public async Task BuildWithExplicitValue(string configuration, int debugLevel) + { + SetupProject($"DebugLevelTests_BuildWithExplicitValue_{configuration}"); + BuildProject(configuration, assertAppBundle: false, extraArgs: $"-p:WasmDebugLevel={debugLevel}"); + + var result = await RunForBuild(configuration); + AssertDebugLevel(result, debugLevel); + } + + [Theory] + [InlineData("Debug")] + [InlineData("Release")] + public async Task PublishWithDefaultLevel(string configuration) + { + SetupProject($"DebugLevelTests_PublishWithDefaultLevel_{configuration}"); + PublishProject(configuration, assertAppBundle: false); + + var result = await RunForPublish(configuration); + AssertDebugLevel(result, 0); + } + + [Theory] + [InlineData("Debug", 1)] + [InlineData("Release", 1)] + [InlineData("Debug", -1)] + [InlineData("Release", -1)] + public async Task PublishWithExplicitValue(string configuration, int debugLevel) + { + SetupProject($"DebugLevelTests_PublishWithExplicitValue_{configuration}"); + PublishProject(configuration, assertAppBundle: false, extraArgs: $"-p:WasmDebugLevel={debugLevel}"); + + var result = await RunForPublish(configuration); + AssertDebugLevel(result, debugLevel); + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmAppBuilderDebugLevelTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmAppBuilderDebugLevelTests.cs new file mode 100644 index 00000000000000..83809b3c2a9a3a --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmAppBuilderDebugLevelTests.cs @@ -0,0 +1,70 @@ +// 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.Linq; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests.TestAppScenarios; + +public class WasmAppBuilderDebugLevelTests : DebugLevelTestsBase +{ + public WasmAppBuilderDebugLevelTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + protected override void SetupProject(string projectId) + { + Id = $"{projectId}_{GetRandomId()}"; + string projectfile = CreateWasmTemplateProject(Id, "wasmconsole"); + string projectDir = Path.GetDirectoryName(projectfile)!; + string mainJs = Path.Combine(projectDir, "main.mjs"); + string mainJsContent = File.ReadAllText(mainJs); + mainJsContent = mainJsContent + .Replace("import { dotnet }", "import { dotnet, exit }") + .Replace("await runMainAndExit()", "console.log('TestOutput -> WasmDebugLevel: ' + config.debugLevel); exit(0)"); + File.WriteAllText(mainJs, mainJsContent); + } + + protected override Task RunForBuild(string configuration) + { + CommandResult res = new RunCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(_projectDir!) + .ExecuteWithCapturedOutput($"run --no-silent --no-build -c {configuration}"); + + return Task.FromResult(ProcessRunOutput(res)); + } + + private RunResult ProcessRunOutput(CommandResult res) + { + var output = res.Output.Split(Environment.NewLine); + _testOutput.WriteLine($"DEBUG: parsed lines '{String.Join(", ", output)}'"); + + var prefix = "[] TestOutput -> "; + var testOutput = output + .Where(l => l.StartsWith(prefix)) + .Select(l => l.Substring(prefix.Length)) + .ToArray(); + + _testOutput.WriteLine($"DEBUG: testOutput '{String.Join(", ", testOutput)}'"); + return new RunResult(res.ExitCode, testOutput, output, []); + } + + protected override Task RunForPublish(string configuration) + { + // WasmAppBuilder does publish to the same folder as build (it overrides the output), + // and thus using dotnet run work correctly for publish as well. + CommandResult res = new RunCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(_projectDir!) + .ExecuteWithCapturedOutput($"run --no-silent --no-build -c {configuration}"); + + return Task.FromResult(ProcessRunOutput(res)); + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmSdkDebugLevelTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmSdkDebugLevelTests.cs new file mode 100644 index 00000000000000..b3d1a0316c49e6 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmSdkDebugLevelTests.cs @@ -0,0 +1,46 @@ +// 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.Linq; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests.TestAppScenarios; + +public class WasmSdkDebugLevelTests : DebugLevelTestsBase +{ + public WasmSdkDebugLevelTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + protected override void SetupProject(string projectId) => CopyTestAsset("WasmBasicTestApp", projectId, "App"); + + protected override Task RunForBuild(string configuration) => RunSdkStyleAppForBuild(new( + Configuration: configuration, + TestScenario: "DebugLevelTest" + )); + + protected override Task RunForPublish(string configuration) => RunSdkStyleAppForPublish(new( + Configuration: configuration, + TestScenario: "DebugLevelTest" + )); + + [Theory] + [InlineData("Debug")] + [InlineData("Release")] + public async Task PublishWithDefaultLevelAndPdbs(string configuration) + { + SetupProject($"DebugLevelTests_PublishWithDefaultLevelAndPdbs_{configuration}"); + PublishProject(configuration, assertAppBundle: false, extraArgs: $"-p:CopyOutputSymbolsToPublishDirectory=true"); + + var result = await RunForPublish(configuration); + AssertDebugLevel(result, -1); + } +} diff --git a/src/mono/wasm/build/WasmApp.Common.targets b/src/mono/wasm/build/WasmApp.Common.targets index 54a202840b41ff..98ef4fcd61bdc3 100644 --- a/src/mono/wasm/build/WasmApp.Common.targets +++ b/src/mono/wasm/build/WasmApp.Common.targets @@ -446,7 +446,7 @@ + Properties="_WasmInNestedPublish_UniqueProperty_XYZ=true;;WasmBuildingForNestedPublish=true;DeployOnBuild=;_IsPublishing=;_WasmIsPublishing=$(_IsPublishing)"> diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs index 2d075541db17bd..d146613f16f44b 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -10,7 +10,7 @@ namespace Microsoft.NET.Sdk.WebAssembly { - public class BootJsonBuilderHelper(TaskLoggingHelper Log, bool IsMultiThreaded) + public class BootJsonBuilderHelper(TaskLoggingHelper Log, string DebugLevel, bool IsMultiThreaded, bool IsPublish) { private static readonly string[] coreAssemblyNames = [ "System.Private.CoreLib", @@ -106,5 +106,35 @@ static void AddDictionary(StringBuilder sb, Dictionary? res) return null; } + + public int GetDebugLevel(bool hasPdb) + { + int? debugLevel = ParseOptionalInt(DebugLevel); + + // If user didn't give us a value, check if we have any PDB. + if (debugLevel == null && hasPdb) + debugLevel = -1; + + // Fallback to -1 for build, or 0 for publish + debugLevel ??= IsPublish ? 0 : -1; + + return debugLevel.Value; + } + + public bool? ParseOptionalBool(string value) + { + if (string.IsNullOrEmpty(value) || !bool.TryParse(value, out var boolValue)) + return null; + + return boolValue; + } + + public int? ParseOptionalInt(string value) + { + if (string.IsNullOrEmpty(value) || !int.TryParse(value, out var intValue)) + return null; + + return intValue; + } } } diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index fbe0aa7e3733e9..33f3debdd4ef82 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -101,12 +101,12 @@ public override bool Execute() // Internal for tests public void WriteBootJson(Stream output, string entryAssemblyName) { - var helper = new BootJsonBuilderHelper(Log, IsMultiThreaded); + var helper = new BootJsonBuilderHelper(Log, DebugLevel, IsMultiThreaded, IsPublish); var result = new BootJsonData { resources = new ResourcesData(), - startupMemoryCache = ParseOptionalBool(StartupMemoryCache), + startupMemoryCache = helper.ParseOptionalBool(StartupMemoryCache), }; if (IsTargeting80OrLater()) @@ -136,7 +136,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName) result.runtimeOptions = runtimeOptions; } - bool? jiterpreter = ParseOptionalBool(Jiterpreter); + bool? jiterpreter = helper.ParseOptionalBool(Jiterpreter); if (jiterpreter != null) { var runtimeOptions = result.runtimeOptions?.ToHashSet() ?? new HashSet(3); @@ -357,16 +357,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName) if (IsTargeting80OrLater()) { - int? debugLevel = ParseOptionalInt(DebugLevel); - - // If user didn't give us a value, check if we have any PDB. - if (debugLevel == null && result.resources?.pdb?.Count > 0) - debugLevel = -1; - - // Fallback to -1 for build, or 0 for publish - debugLevel ??= IsPublish ? 0 : -1; - - result.debugLevel = debugLevel.Value; + result.debugLevel = helper.GetDebugLevel(result.resources?.pdb?.Count > 0); } if (ConfigurationFiles != null) @@ -434,22 +425,6 @@ private GlobalizationMode GetGlobalizationMode() return GlobalizationMode.Sharded; } - private static bool? ParseOptionalBool(string value) - { - if (string.IsNullOrEmpty(value) || !bool.TryParse(value, out var boolValue)) - return null; - - return boolValue; - } - - private static int? ParseOptionalInt(string value) - { - if (string.IsNullOrEmpty(value) || !int.TryParse(value, out var intValue)) - return null; - - return intValue; - } - private void AddToAdditionalResources(ITaskItem resource, Dictionary additionalResources, string resourceName, string behavior) { if (!additionalResources.ContainsKey(resourceName)) diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index b16c21e117fd73..9fca78e4263fa1 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -30,7 +30,8 @@ public class WasmAppBuilder : WasmAppBuilderBaseTask public string? WasmIcuDataFileName { get; set; } public string? RuntimeAssetsLocation { get; set; } public bool CacheBootResources { get; set; } - public int DebugLevel { get; set; } + public string? DebugLevel { get; set; } + public bool IsPublish { get; set; } public bool IsAot { get; set; } public bool IsMultiThreaded { get; set; } @@ -97,7 +98,7 @@ private GlobalizationMode GetGlobalizationMode() protected override bool ExecuteInternal() { - var helper = new BootJsonBuilderHelper(Log, IsMultiThreaded); + var helper = new BootJsonBuilderHelper(Log, DebugLevel!, IsMultiThreaded, IsPublish); var logAdapter = new LogAdapter(Log); if (!ValidateArguments()) @@ -133,6 +134,8 @@ protected override bool ExecuteInternal() if (UseWebcil) Log.LogMessage(MessageImportance.Normal, "Converting assemblies to Webcil"); + int baseDebugLevel = helper.GetDebugLevel(false); + foreach (var assembly in _assemblies) { if (UseWebcil) @@ -151,7 +154,7 @@ protected override bool ExecuteInternal() { FileCopyChecked(assembly, Path.Combine(runtimeAssetsPath, Path.GetFileName(assembly)), "Assemblies"); } - if (DebugLevel != 0) + if (baseDebugLevel != 0) { var pdb = assembly; pdb = Path.ChangeExtension(pdb, ".pdb"); @@ -217,7 +220,7 @@ protected override bool ExecuteInternal() var assemblyList = isCoreAssembly ? bootConfig.resources.coreAssembly : bootConfig.resources.assembly; assemblyList[assemblyName] = Utils.ComputeIntegrity(bytes); - if (DebugLevel != 0) + if (baseDebugLevel != 0) { var pdb = Path.ChangeExtension(assembly, ".pdb"); if (File.Exists(pdb)) @@ -240,7 +243,7 @@ protected override bool ExecuteInternal() } } - bootConfig.debugLevel = DebugLevel; + bootConfig.debugLevel = helper.GetDebugLevel(bootConfig.resources.pdb?.Count > 0); ProcessSatelliteAssemblies(args => {