Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance support for configuring debuggers used for Blazor WASM apps #2774

Merged
merged 4 commits into from
Nov 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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": {
captainsafia marked this conversation as resolved.
Show resolved Hide resolved
"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',
captainsafia marked this conversation as resolved.
Show resolved Hide resolved
...configuration.env,
},
logging: configuration.logging,
launchBrowser: {
enabled: false,
},
...configuration.dotNetConfig,
};

this.vscodeType.debug.startDebugging(folder, app).then((appStartFulfilled: boolean) => {
captainsafia marked this conversation as resolved.
Show resolved Hide resolved
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a separate category of #PranavPoints for Typescript?

};

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...');
captainsafia marked this conversation as resolved.
Show resolved Hide resolved
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)));
captainsafia marked this conversation as resolved.
Show resolved Hide resolved
targetPid = devserver ? devserver.pid : undefined;

killProcess(targetPid, logger);
Expand Down