Skip to content

Commit

Permalink
Enhance support for configuring debuggers used for Blazor WASM apps (#…
Browse files Browse the repository at this point in the history
…2774)

* Use .NET debugger to launch app server
* Add support for config properties and fix launch
* Update options description
* Add supported config options to options
  • Loading branch information
captainsafia authored Nov 30, 2020
1 parent 3cb980f commit a76afe2
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 99 deletions.
113 changes: 76 additions & 37 deletions src/Razor/src/Microsoft.AspNetCore.Razor.VSCode.Extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,51 +114,90 @@
"type": "object",
"description": "Environment variables passed to dotnet. Only valid for hosted apps."
},
"logging": {
"description": "Optional flags to determine what types of messages should be logged to the output window. Applicable only for the app server of hosted Blazor WASM apps.",
"dotNetConfig": {
"description": "Options passed to the underlying .NET debugger. For more info, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger.md.",
"type": "object",
"required": [],
"default": {},
"properties": {
"exceptions": {
"justMyCode": {
"type": "boolean",
"description": "Optional flag to determine whether exception messages should be logged to the output window.",
"description": "Optional flag to only show user code.",
"default": true
},
"moduleLoad": {
"type": "boolean",
"description": "Optional flag to determine whether module load events should be logged to the output window.",
"default": true
},
"programOutput": {
"type": "boolean",
"description": "Optional flag to determine whether program output should be logged to the output window when not using an external console.",
"default": true
},
"engineLogging": {
"type": "boolean",
"description": "Optional flag to determine whether diagnostic engine logs should be logged to the output window.",
"default": false
},
"browserStdOut": {
"type": "boolean",
"description": "Optional flag to determine if stdout text from the launching the web browser should be logged to the output window.",
"default": true
},
"elapsedTiming": {
"type": "boolean",
"description": "If true, engine logging will include `adapterElapsedTime` and `engineElapsedTime` properties to indicate the amount of time, in microseconds, that a request took.",
"default": false
"sourceFileMap": {
"type": "object",
"description": "Optional source file mappings passed to the debug engine. Example: '{ \"C:\\foo\":\"/home/user/foo\" }'",
"additionalProperties": {
"type": "string"
},
"default": {
"<insert-source-path-here>": "<insert-target-path-here>"
}
},
"threadExit": {
"type": "boolean",
"description": "Controls if a message is logged when a thread in the target process exits. Default: `false`.",
"default": false
},
"processExit": {
"type": "boolean",
"description": "Controls if a message is logged when the target process exits, or debugging is stopped. Default: `true`.",
"default": true
"logging": {
"description": "Optional flags to determine what types of messages should be logged to the output window.",
"type": "object",
"required": [],
"default": {},
"properties": {
"exceptions": {
"type": "boolean",
"description": "Optional flag to determine whether exception messages should be logged to the output window.",
"default": true
},
"moduleLoad": {
"type": "boolean",
"description": "Optional flag to determine whether module load events should be logged to the output window.",
"default": true
},
"programOutput": {
"type": "boolean",
"description": "Optional flag to determine whether program output should be logged to the output window when not using an external console.",
"default": true
},
"engineLogging": {
"type": "boolean",
"description": "Optional flag to determine whether diagnostic engine logs should be logged to the output window.",
"default": false
},
"browserStdOut": {
"type": "boolean",
"description": "Optional flag to determine if stdout text from the launching the web browser should be logged to the output window.",
"default": true
},
"elapsedTiming": {
"type": "boolean",
"description": "If true, engine logging will include `adapterElapsedTime` and `engineElapsedTime` properties to indicate the amount of time, in microseconds, that a request took.",
"default": false
},
"threadExit": {
"type": "boolean",
"description": "Controls if a message is logged when a thread in the target process exits. Default: `false`.",
"default": false
},
"processExit": {
"type": "boolean",
"description": "Controls if a message is logged when the target process exits, or debugging is stopped. Default: `true`.",
"default": true
}
}
}
}
},
"browserConfig": {
"description": "Options based to the underlying JavaScript debugger. For more info, see https://github.com/microsoft/vscode-js-debug/blob/master/OPTIONS.md.",
"type": "object",
"required": [],
"default": {},
"properties": {
"outputCapture": {
"enum": [
"console",
"std"
],
"description": "From where to capture output messages: the default debug API if set to `console`, or stdout/stderr streams if set to `std`.",
"default": "console"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import * as vscode from 'vscode';

import { RazorLogger } from '../RazorLogger';
import { HOSTED_APP_NAME, JS_DEBUG_NAME } from './Constants';
import { JS_DEBUG_NAME, SERVER_APP_NAME } from './Constants';
import { onDidTerminateDebugSession } from './TerminateDebugHandler';

export class BlazorDebugConfigurationProvider implements vscode.DebugConfigurationProvider {
Expand All @@ -20,90 +20,65 @@ export class BlazorDebugConfigurationProvider implements vscode.DebugConfigurati
* only launch the browser.
*/
if (configuration.request === 'launch') {
/**
* If the user is debugging a hosted Blazor WebAssembly application,
* then the application server needs to be launched via VS Code's debug
* workflow to allow debugging on the server-side logic. If the app is
* not hosted, then it can be spun as a standalone process to avoid additional overhead.
*/
if (configuration.hosted) {
this.launchHostedApp(folder, configuration);
} else {
this.launchStandaloneApp(folder, configuration);
}
await this.launchApp(folder, configuration);
}

await this.launchBrowser(folder, configuration);
this.vscodeType.debug.onDidStartDebugSession(async event => {
if (event.name === SERVER_APP_NAME) {
await this.launchBrowser(folder, configuration, event);
}
});

/**
* If `resolveDebugConfiguration` returns undefined, then the debugger
* launch is canceled. Here, we opt to manually launch the browser
* configruation using `startDebugging` above instead of returning
* configuration using `startDebugging` above instead of returning
* the configuration to avoid a bug where VS Code is unable to resolve
* the debug adapter for the browser debugger.
*/
return undefined;
}

private launchHostedApp(folder: vscode.WorkspaceFolder | undefined, configuration: vscode.DebugConfiguration) {
if (!configuration.program && !configuration.cwd) {
const message = `Must provide 'program' and 'cwd' properties in launch configuration for hosted Blazor WebAssembly apps.`;
this.vscodeType.window.showErrorMessage(message);
}
private async launchApp(folder: vscode.WorkspaceFolder | undefined, configuration: vscode.DebugConfiguration) {
const program = configuration.hosted ? configuration.program : 'dotnet';
const cwd = configuration.cwd || '${workspaceFolder}';
const args = configuration.hosted ? [] : ['run'];

const app = {
name: HOSTED_APP_NAME,
name: SERVER_APP_NAME,
type: 'coreclr',
request: 'launch',
preLaunchTask: 'build',
program: configuration.program,
args: [],
cwd: configuration.cwd,
prelaunchTask: 'build',
program,
args,
cwd,
env: {
ASPNETCORE_ENVIRONMENT: 'Development',
...configuration.env,
},
logging: configuration.logging,
launchBrowser: {
enabled: false,
},
...configuration.dotNetConfig,
};

this.vscodeType.debug.startDebugging(folder, app).then((appStartFulfilled: boolean) => {
this.logger.logVerbose('[DEBUGGER] Launching hosted Blazor WebAssembly app...');
try {
await this.vscodeType.debug.startDebugging(folder, app);
if (process.platform !== 'win32') {
const terminate = this.vscodeType.debug.onDidTerminateDebugSession(async event => {
await onDidTerminateDebugSession(event, this.logger, app.program);
const blazorDevServer = 'blazor-devserver.dll';
const dir = folder && folder.uri && folder.uri.fsPath;
const launchedApp = configuration.hosted ? app.program : `${dir}.*${blazorDevServer}|${blazorDevServer}.*${dir}`;
await onDidTerminateDebugSession(event, this.logger, launchedApp);
terminate.dispose();
});
}
}, (error: Error) => {
} catch (error) {
this.logger.logError('[DEBUGGER] Error when launching application: ', error);
});
}

private launchStandaloneApp(folder: vscode.WorkspaceFolder | undefined, configuration: vscode.DebugConfiguration) {
const shellPath = process.platform === 'win32' ? 'cmd.exe' : 'dotnet';
const shellArgs = process.platform === 'win32' ? ['/c', 'chcp 65001 >NUL & dotnet run'] : ['run'];
const spawnOptions = {
cwd: configuration.cwd || (folder && folder.uri && folder.uri.fsPath),
};

const output = this.vscodeType.window.createTerminal({
name: 'Blazor WebAssembly App',
shellPath,
shellArgs,
...spawnOptions,
});

/**
* We need to terminate the Blazor dev server.
*/
const terminate = this.vscodeType.debug.onDidTerminateDebugSession(async event => {
await onDidTerminateDebugSession(event, this.logger, await output.processId);
terminate.dispose();
});

output.show(/*preserveFocus*/true);
}
}

private async launchBrowser(folder: vscode.WorkspaceFolder | undefined, configuration: vscode.DebugConfiguration) {
private async launchBrowser(folder: vscode.WorkspaceFolder | undefined, configuration: vscode.DebugConfiguration, parentSession?: vscode.DebugSession) {
const browser = {
name: JS_DEBUG_NAME,
type: configuration.browser === 'edge' ? 'pwa-msedge' : 'pwa-chrome',
Expand All @@ -114,6 +89,7 @@ export class BlazorDebugConfigurationProvider implements vscode.DebugConfigurati
inspectUri: '{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}',
trace: configuration.trace || false,
noDebug: configuration.noDebug || false,
...configuration.browserConfig,
};

try {
Expand All @@ -126,8 +102,7 @@ export class BlazorDebugConfigurationProvider implements vscode.DebugConfigurati
* We do this to provide immediate visual feedback to the user
* that their debugger session has started.
*/
await this.vscodeType.debug.startDebugging(folder, browser);
this.logger.logVerbose('[DEBUGGER] Launching browser debugger...');
await this.vscodeType.debug.startDebugging(folder, browser, parentSession);
} catch (error) {
this.logger.logError(
'[DEBUGGER] Error when launching browser debugger: ',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */

export const HOSTED_APP_NAME = '.NET Core Launch (Blazor Hosted)';
export const SERVER_APP_NAME = '.NET Application Server';
export const JS_DEBUG_NAME = 'Debug Blazor Web Assembly in Browser';
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { DebugSession } from 'vscode';

import { RazorLogger } from '../RazorLogger';

import { HOSTED_APP_NAME, JS_DEBUG_NAME } from './Constants';
import { JS_DEBUG_NAME, SERVER_APP_NAME } from './Constants';

const isValidEvent = (name: string) => {
const VALID_EVENT_NAMES = [HOSTED_APP_NAME, JS_DEBUG_NAME];
const VALID_EVENT_NAMES = [SERVER_APP_NAME, JS_DEBUG_NAME];
if (!VALID_EVENT_NAMES.includes(name)) {
return false;
}
Expand Down Expand Up @@ -80,7 +80,7 @@ async function terminateByProcessName(
}

const devserver = processes.find(
(process: psList.ProcessDescriptor) => !!(process && process.cmd && process.cmd.includes(targetProcess)));
(process: psList.ProcessDescriptor) => !!(process && process.cmd && process.cmd.match(targetProcess)));
targetPid = devserver ? devserver.pid : undefined;

killProcess(targetPid, logger);
Expand Down

0 comments on commit a76afe2

Please sign in to comment.