Skip to content

Commit 22b51ae

Browse files
authored
Do not set IsDebuggedProcess for legacy WASM debugger (#9804)
* Do not set IsDebuggedProcess for legacy WASM debugger * Fix * Disable trace listener
1 parent abeccf3 commit 22b51ae

File tree

10 files changed

+156
-11
lines changed

10 files changed

+156
-11
lines changed

src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/FeatureFlagNames.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,9 @@ internal static class FeatureFlagNames
2727
/// or out via the <c>AccelerateBuildsInVisualStudio</c> MSBuild property.
2828
/// </summary>
2929
public const string EnableBuildAccelerationByDefault = "ManagedProjectSystem.EnableBuildAccelerationByDefault";
30+
31+
/// <summary>
32+
/// Enables new WASM debugger.
33+
/// </summary>
34+
public const string EnableCorDebugWebAssemblyDebugger = "Debugger.NewMonoDebugEngine";
3035
}

src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/ProjectSystemOptions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ public ValueTask<bool> IsBuildAccelerationEnabledByDefaultAsync(CancellationToke
8989
return IsFlagEnabledAsync(FeatureFlagNames.EnableBuildAccelerationByDefault, defaultValue: false, cancellationToken);
9090
}
9191

92+
public ValueTask<bool> IsCorDebugWebAssemblyDebuggerEnabledAsync(CancellationToken cancellationToken)
93+
=> IsFlagEnabledAsync(FeatureFlagNames.EnableCorDebugWebAssemblyDebugger, defaultValue: false, cancellationToken);
94+
9295
private async ValueTask<bool> IsFlagEnabledAsync(string featureName, bool defaultValue, CancellationToken cancellationToken)
9396
{
9497
IVsFeatureFlags featureFlags = await _featureFlagsService.GetValueAsync(cancellationToken);

src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/HotReload/ProjectHotReloadAgent.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace Microsoft.VisualStudio.ProjectSystem.HotReload;
1111
internal sealed class ProjectHotReloadAgent(
1212
Lazy<IHotReloadAgentManagerClient> hotReloadAgentManagerClient,
1313
Lazy<IHotReloadDiagnosticOutputService> hotReloadDiagnosticOutputService,
14+
IProjectSystemOptions projectSystemOptions,
1415
[Import(AllowDefault = true)] IHotReloadDebugStateProvider? debugStateProvider) // allow default until VS Code is updated: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2571211
1516
: IProjectHotReloadAgent
1617
{
@@ -33,6 +34,7 @@ public IProjectHotReloadSession CreateHotReloadSession(
3334
configuredProject: configuredProject,
3435
launchProfile: launchProfile,
3536
debugLaunchOptions: debugLaunchOptions,
37+
projectSystemOptions,
3638
debugStateProvider ?? DefaultDebugStateProvider.Instance);
3739
}
3840

src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/HotReload/ProjectHotReloadSession.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ internal sealed class ProjectHotReloadSession : IProjectHotReloadSessionInternal
2323
private readonly IProjectHotReloadBuildManager _buildManager;
2424
private readonly ILaunchProfile _launchProfile;
2525
private readonly DebugLaunchOptions _debugLaunchOptions;
26+
private readonly IProjectSystemOptions _projectSystemOptions;
2627
private readonly IHotReloadDebugStateProvider _debugStateProvider;
2728
private readonly IProjectHotReloadLaunchProvider _launchProvider;
2829

@@ -40,6 +41,7 @@ public ProjectHotReloadSession(
4041
ConfiguredProject configuredProject,
4142
ILaunchProfile launchProfile,
4243
DebugLaunchOptions debugLaunchOptions,
44+
IProjectSystemOptions projectSystemOptions,
4345
IHotReloadDebugStateProvider debugStateProvider)
4446
{
4547
Name = name;
@@ -53,6 +55,7 @@ public ProjectHotReloadSession(
5355
_buildManager = buildManager;
5456
_launchProvider = launchProvider;
5557
_debugLaunchOptions = debugLaunchOptions;
58+
_projectSystemOptions = projectSystemOptions;
5659
_debugStateProvider = debugStateProvider;
5760
}
5861

@@ -156,8 +159,6 @@ public async Task StartSessionAsync(CancellationToken cancellationToken)
156159
throw new InvalidOperationException("Attempting to start a Hot Reload session that is already running.");
157160
}
158161

159-
HotReloadAgentFlags flags = _debugLaunchOptions.HasFlag(DebugLaunchOptions.NoDebug) ? HotReloadAgentFlags.None : HotReloadAgentFlags.IsDebuggedProcess;
160-
161162
string targetFramework = await _configuredProject.GetProjectPropertyValueAsync(ConfigurationGeneral.TargetFrameworkProperty);
162163
bool hotReloadAutoRestart = await _configuredProject.GetProjectPropertyBoolAsync(ConfigurationGeneral.HotReloadAutoRestartProperty);
163164

@@ -174,6 +175,14 @@ public async Task StartSessionAsync(CancellationToken cancellationToken)
174175
DebugTrace($"Start session for project '{_configuredProject.UnconfiguredProject.FullPath}' with TFM '{targetFramework}' and HotReloadRestart {runningProjectInfo.RestartAutomatically}");
175176

176177
var processInfo = new ManagedEditAndContinueProcessInfo();
178+
179+
// If the debugger uses ICorDebug, the debugger is responsible for applying deltas to the process and the Hot Reload client receives empty deltas.
180+
// Otherwise, the Hot Reload client is responsible for applying deltas and needs to receive them from the debugger.
181+
// This is controlled by IsDebuggedProcess flag.
182+
var flags = _debugLaunchOptions.HasFlag(DebugLaunchOptions.NoDebug) || await UsingLegacyWebAssemblyDebugEngineAsync(cancellationToken)
183+
? HotReloadAgentFlags.None
184+
: HotReloadAgentFlags.IsDebuggedProcess;
185+
177186
await _hotReloadAgentManagerClient.Value.AgentStartedAsync(this, flags, processInfo, runningProjectInfo, cancellationToken);
178187

179188
WriteToOutputWindow(Resources.HotReloadStartSession, default);
@@ -186,6 +195,31 @@ public async Task StartSessionAsync(CancellationToken cancellationToken)
186195
_sessionActive = true;
187196
}
188197

198+
private async ValueTask<bool> UsingLegacyWebAssemblyDebugEngineAsync(CancellationToken cancellationToken)
199+
{
200+
if (_callback is not IProjectHotReloadSessionWebAssemblyCallback)
201+
{
202+
// not a WASM app:
203+
return false;
204+
}
205+
206+
if (await _projectSystemOptions.IsCorDebugWebAssemblyDebuggerEnabledAsync(cancellationToken) &&
207+
await IsCorDebugWebAssemblyDebuggerSupportedByProjectAsync())
208+
{
209+
// using new debugger:
210+
return false;
211+
}
212+
213+
// using old debugger:
214+
return true;
215+
216+
async ValueTask<bool> IsCorDebugWebAssemblyDebuggerSupportedByProjectAsync()
217+
{
218+
var targetFrameworkMoniker = await _configuredProject.GetProjectPropertyValueAsync(ConfigurationGeneral.TargetFrameworkMonikerProperty);
219+
return new FrameworkName(targetFrameworkMoniker).Version.Major >= 9;
220+
}
221+
}
222+
189223
public async Task StopSessionAsync(CancellationToken cancellationToken)
190224
{
191225
if (_sessionActive && _lazyDeltaApplier is not null)

src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/IProjectSystemOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,9 @@ internal interface IProjectSystemOptions
6565
/// or out via the <c>AccelerateBuildsInVisualStudio</c> MSBuild property.
6666
/// </summary>
6767
ValueTask<bool> IsBuildAccelerationEnabledByDefaultAsync(CancellationToken cancellationToken);
68+
69+
/// <summary>
70+
/// True if the WASM debugger engine uses ICorDebug.
71+
/// </summary>
72+
ValueTask<bool> IsCorDebugWebAssemblyDebuggerEnabledAsync(CancellationToken cancellationToken);
6873
}

tests/Common/Test/App.config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<add key="xunit.methodDisplay" value="method" />
55
<add key="xunit.shadowCopy" value="false"/>
66
</appSettings>
7+
<!-- TODO: https://github.com/dotnet/project-system/issues/9805
78
<system.diagnostics>
89
<trace>
910
<listeners>
@@ -12,4 +13,5 @@
1213
</listeners>
1314
</trace>
1415
</system.diagnostics>
16+
-->
1517
</configuration>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.
2+
3+
using Microsoft.DotNet.HotReload;
4+
using Microsoft.Extensions.Logging;
5+
using Microsoft.VisualStudio.ProjectSystem.VS.HotReload;
6+
7+
namespace Microsoft.VisualStudio.ProjectSystem.VS;
8+
9+
internal static class IProjectHotReloadSessionWebAssemblyCallbackFactory
10+
{
11+
public static IProjectHotReloadSessionWebAssemblyCallback Create()
12+
{
13+
var mock = new Mock<IProjectHotReloadSessionWebAssemblyCallback>();
14+
15+
var middlewarePath = Path.GetTempPath();
16+
var logger = new Mock<ILogger>().Object;
17+
var loggerFactory = new Mock<ILoggerFactory>().Object;
18+
19+
var server = new Mock<AbstractBrowserRefreshServer>(middlewarePath, logger, loggerFactory).Object;
20+
21+
mock.Setup(session => session.BrowserRefreshServerAccessor)
22+
.Returns(new TestBrowserRefreshServerAccessor(server));
23+
24+
return mock.Object;
25+
}
26+
}

tests/Microsoft.VisualStudio.ProjectSystem.Managed.UnitTests/Mocks/IProjectSystemOptionsFactory.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,13 @@ public static IProjectSystemOptions ImplementGetPreferSingleTargetBuildsForStart
4444

4545
return mock.Object;
4646
}
47+
48+
public static IProjectSystemOptions ImplementIsCorDebugWebAssemblyDebuggerEnabledAsync(bool value)
49+
{
50+
var mock = new Mock<IProjectSystemOptions>();
51+
mock.Setup(o => o.IsCorDebugWebAssemblyDebuggerEnabledAsync(It.IsAny<CancellationToken>()))
52+
.ReturnsAsync(value);
53+
54+
return mock.Object;
55+
}
4756
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.
2+
3+
using Microsoft.DotNet.HotReload;
4+
using Microsoft.VisualStudio.ProjectSystem.HotReload;
5+
6+
namespace Microsoft.VisualStudio.ProjectSystem.VS;
7+
8+
internal class TestBrowserRefreshServerAccessor(AbstractBrowserRefreshServer server) : AbstractBrowserRefreshServerAccessor
9+
{
10+
internal override AbstractBrowserRefreshServer Server => server;
11+
}

tests/Microsoft.VisualStudio.ProjectSystem.Managed.UnitTests/ProjectSystem/HotReload/ProjectHotReloadSessionTests.cs

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,10 @@ public async Task StartSessionAsync_VerifiesCorrectProcessInfoAndProjectInfo()
140140
const string expectedProjectPath = "C:\\Test\\Project.csproj";
141141
const string expectedTf = "net6.0";
142142

143-
var configuredProject = CreateConfiguredProjectWithCommonProperties(expectedTf, expectedProjectPath);
143+
var configuredProject = CreateConfiguredProjectWithCommonProperties(
144+
projectPath: expectedProjectPath,
145+
targetFrameworkIdentifier: ".NETCoreApp",
146+
targetFrameworkVersion: "6.0");
144147

145148
var session = CreateInstance(
146149
hotReloadAgentManagerClient: new Lazy<IHotReloadAgentManagerClient>(() => hotReloadAgentManagerClient.Object),
@@ -193,7 +196,11 @@ public async Task StartSessionAsync_CallsAgentStartedWithCorrectParameters()
193196
{
194197
// Arrange
195198
var hotReloadAgentManagerClient = new Mock<IHotReloadAgentManagerClient>();
196-
var configuredProject = CreateConfiguredProjectWithCommonProperties("net6.0", "C:\\Test\\Project.csproj");
199+
var configuredProject = CreateConfiguredProjectWithCommonProperties(
200+
"C:\\Test\\Project.csproj",
201+
targetFrameworkIdentifier: ".NETCoreApp",
202+
targetFrameworkVersion: "6.0");
203+
197204
var cancellationToken = CancellationToken.None;
198205

199206
RunningProjectInfo capturedProjectInfo = default;
@@ -245,18 +252,36 @@ public async Task StartSessionAsync_CallsAgentStartedWithCorrectParameters()
245252
Times.Once);
246253
}
247254

248-
[Fact]
249-
public async Task StartSessionAsync_WithDebugger_SetsCorrectFlags()
255+
[Theory]
256+
// not debugging:
257+
[InlineData(false, true, false, "6.0", HotReloadAgentFlags.None)]
258+
[InlineData(false, true, true, "6.0", HotReloadAgentFlags.None)]
259+
// debugging non-wasm project:
260+
[InlineData(true, false, false, "6.0", HotReloadAgentFlags.IsDebuggedProcess)]
261+
// debugging wasm project using legacy debugger:
262+
[InlineData(true, true, false, "6.0", HotReloadAgentFlags.None)]
263+
[InlineData(true, true, true, "6.0", HotReloadAgentFlags.None)]
264+
[InlineData(true, true, true, "7.0", HotReloadAgentFlags.None)]
265+
[InlineData(true, true, true, "8.0", HotReloadAgentFlags.None)]
266+
// debugging wasm project using new debugger:
267+
[InlineData(true, true, true, "9.0", HotReloadAgentFlags.IsDebuggedProcess)]
268+
[InlineData(true, true, true, "10.0", HotReloadAgentFlags.IsDebuggedProcess)]
269+
public async Task StartSessionAsync_WithDebugger_SetsCorrectFlags(bool isDebugging, bool isWasmProject, bool isWasmCorDebugEnabled, string targetFrameworkVersion, HotReloadAgentFlags expectedFlags)
250270
{
251271
// Arrange
252272
var hotReloadAgentManagerClient = new Mock<IHotReloadAgentManagerClient>();
253-
var configuredProject = CreateConfiguredProjectWithCommonProperties();
273+
var configuredProject = CreateConfiguredProjectWithCommonProperties(
274+
targetFrameworkIdentifier: ".NETCoreApp",
275+
targetFrameworkVersion: targetFrameworkVersion);
276+
254277
var cancellationToken = CancellationToken.None;
255278

256279
var session = CreateInstance(
257280
hotReloadAgentManagerClient: new Lazy<IHotReloadAgentManagerClient>(() => hotReloadAgentManagerClient.Object),
258281
configuredProject: configuredProject,
259-
debugLaunchOptions: 0);
282+
debugLaunchOptions: isDebugging ? 0 : DebugLaunchOptions.NoDebug,
283+
projectSystemOptions: IProjectSystemOptionsFactory.ImplementIsCorDebugWebAssemblyDebuggerEnabledAsync(isWasmCorDebugEnabled),
284+
callback: isWasmProject ? IProjectHotReloadSessionWebAssemblyCallbackFactory.Create() : null);
260285

261286
// Act
262287
await session.StartSessionAsync(cancellationToken);
@@ -265,7 +290,7 @@ public async Task StartSessionAsync_WithDebugger_SetsCorrectFlags()
265290
hotReloadAgentManagerClient.Verify(
266291
client => client.AgentStartedAsync(
267292
session,
268-
HotReloadAgentFlags.IsDebuggedProcess,
293+
expectedFlags,
269294
It.IsAny<ManagedEditAndContinueProcessInfo>(),
270295
It.IsAny<RunningProjectInfo>(),
271296
cancellationToken),
@@ -561,7 +586,8 @@ private static ProjectHotReloadSession CreateInstance(
561586
DebugLaunchOptions debugLaunchOptions = DebugLaunchOptions.NoDebug,
562587
IProjectHotReloadBuildManager? buildManager = null,
563588
IProjectHotReloadLaunchProvider? launchProvider = null,
564-
IHotReloadDebugStateProvider? debugStateProvider = null)
589+
IHotReloadDebugStateProvider? debugStateProvider = null,
590+
IProjectSystemOptions? projectSystemOptions = null)
565591
{
566592
hotReloadAgentManagerClient ??= new(Mock.Of<IHotReloadAgentManagerClient>);
567593
hotReloadOutputService ??= new(Mock.Of<IHotReloadDiagnosticOutputService>);
@@ -586,6 +612,7 @@ private static ProjectHotReloadSession CreateInstance(
586612
buildManager ??= new Mock<IProjectHotReloadBuildManager>().Object;
587613
launchProvider ??= new Mock<IProjectHotReloadLaunchProvider>().Object;
588614
configuredProject ??= CreateConfiguredProjectWithCommonProperties();
615+
projectSystemOptions ??= new Mock<IProjectSystemOptions>().Object;
589616

590617
return new ProjectHotReloadSession(
591618
name,
@@ -598,15 +625,36 @@ private static ProjectHotReloadSession CreateInstance(
598625
configuredProject,
599626
launchProfile,
600627
debugLaunchOptions,
628+
projectSystemOptions,
601629
debugStateProvider);
602630
}
603631

604-
private static ConfiguredProject CreateConfiguredProjectWithCommonProperties(string targetFramework = "net6.0", string projectPath = "C:\\Test\\Project.csproj")
632+
private static ConfiguredProject CreateConfiguredProjectWithCommonProperties(
633+
string projectPath = "C:\\Test\\Project.csproj",
634+
string targetFrameworkIdentifier = ".NETCoreApp",
635+
string targetFrameworkVersion = "6.0",
636+
string webAssemblyHotReloadCapabilities = "Baseline")
605637
{
638+
var targetFramework = targetFrameworkIdentifier switch
639+
{
640+
".NETCoreApp" => "net" + targetFrameworkVersion,
641+
".NETFramework" => "net" + targetFrameworkVersion.Replace(".", ""),
642+
".NETStandard" => "netstandard" + targetFrameworkVersion,
643+
_ => throw new ArgumentException()
644+
};
645+
646+
var targetFrameworkMoniker = $"{targetFrameworkIdentifier}, Version=v{targetFrameworkVersion}";
647+
606648
var commonProperties = new Mock<IProjectProperties>();
607649
commonProperties.Setup(p => p.GetEvaluatedPropertyValueAsync(ConfigurationGeneral.TargetFrameworkProperty))
608650
.ReturnsAsync(targetFramework);
609651

652+
commonProperties.Setup(p => p.GetEvaluatedPropertyValueAsync(ConfigurationGeneral.TargetFrameworkMonikerProperty))
653+
.ReturnsAsync(targetFrameworkMoniker);
654+
655+
commonProperties.Setup(p => p.GetEvaluatedPropertyValueAsync("WebAssemblyHotReloadCapabilities"))
656+
.ReturnsAsync(webAssemblyHotReloadCapabilities);
657+
610658
var projectPropertiesProvider = new Mock<IProjectPropertiesProvider>();
611659
projectPropertiesProvider.Setup(p => p.GetCommonProperties())
612660
.Returns(commonProperties.Object);

0 commit comments

Comments
 (0)