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

Add support for one-step debugging for Blazor #1885

Merged
merged 13 commits into from
May 14, 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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"activationEvents": [
"onWebviewPanel:razorReportIssue",
"onDebugInitialConfigurations",
"onDebugResolve:blazorwasm",
"onDebugResolve:coreclr",
"onDebugResolve:clr",
"onLanguage:csharp",
Expand Down Expand Up @@ -46,6 +47,65 @@
],
"main": "./dist/extension",
"contributes": {
"breakpoints": [
{
"language": "aspnetcorerazor"
},
{
"language": "csharp"
NTaylorMullen marked this conversation as resolved.
Show resolved Hide resolved
}
],
"debuggers": [
{
"type": "blazorwasm",
"label": "Blazor WebAssembly Debug",
"initialConfigurations": [
{
"type": "blazorwasm",
"name": "Launch and Debug Blazor WebAssembly Application",
"request": "launch"
}
],
"configurationAttributes": {
"launch": {
"properties": {
"cwd": {
"type": "string",
"description": "The directory of the Blazor WebAssembly app, defaults to the workspace folder.",
"default": "${workspaceFolder}"
},
"url": {
"type": "string",
"description": "The URL of the application",
"default": "https://localhost:5001"
},
"browser": {
"type": "string",
captainsafia marked this conversation as resolved.
Show resolved Hide resolved
"description": "The debugging browser to launch (Edge or Chrome)",
"default": "chrome",
"enum": ["chrome", "edge"]
},
"trace": {
"type": ["boolean", "string"],
"default": "true",
"enum": ["verbose", true],
"description": "If true, verbose logs from JS debugger are sent to log file. If 'verbose', send logs to console."
},
"webRoot": {
"type": "string",
"default": "${workspaceFolder}",
"description": "Specifies the absolute path to the webserver root."
},
"timeout": {
"type": "number",
"default": 30000,
"description": "Retry for this number of milliseconds to connect to browser."
}
}
}
}
}
],
"semanticTokenTypes": [
{
"id": "razorTagHelperElement",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,11 @@ path-parse@^1.0.6:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==

ps-list@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/ps-list/-/ps-list-7.0.0.tgz#fd740a839786605d257117b899031db9b34b8b4b"
integrity sha512-ZDhdxqb+kE895BAvqIdGnWwfvB43h7KHMIcJC0hw7xLbbiJoprS+bqZxuGZ0jWdDxZEvB3jpnfgJyOn3lmsH+Q==

resolve@^1.3.2:
version "1.12.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"typescript": "3.3.4000"
},
"dependencies": {
"ps-list": "^7.0.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

Just remembered, you'll also need to make sure this is tracked in component governance as well: https://dev.azure.com/dnceng/internal/_componentGovernance/dotnet-aspnetcore-tooling?_a=alerts&typeId=1981311&alerts-view-option=active

"vscode-html-languageservice": "2.1.7",
"vscode-languageclient": "5.2.1"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */

import * as vscode from 'vscode';

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

export class BlazorDebugConfigurationProvider implements vscode.DebugConfigurationProvider {

constructor(private readonly logger: RazorLogger, private readonly vscodeType: typeof vscode) { }

public async resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, configuration: vscode.DebugConfiguration): Promise<vscode.DebugConfiguration | undefined> {
const shellPath = process.platform === 'win32' ? 'cmd.exe' : 'dotnet';
Copy link
Contributor

Choose a reason for hiding this comment

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

Missed this in the first pass. @gregg-miskelly do you all do anything special in the case dotnet is not on the path? Wondering if we should be notifying users if it's not.

Copy link
Member Author

Choose a reason for hiding this comment

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

For our implementation, the error will be visible in the terminal in the workspace. Not sure if we want to check for dotnet in the path ahead of time and do something prominent like show an error message.

Choose a reason for hiding this comment

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

Assuming the C# extension is installed (I think it always will be?) the user will get a prompt telling them that dotnet could not be located. That said, lots of people ignore errors. So if you are just showing a generic 'file not found' error, it might be helpful to print out a bit extra. You could link to: https://aka.ms/VSCode-CS-DotnetNotFoundHelp


In reply to: 424869410 [](ancestors = 424869410)

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,
});

/**
* On non-Windows platforms, we need to terminate the Blazor
* dev server and its child processes.
*/
const terminate = this.vscodeType.debug.onDidTerminateDebugSession(async event => {
captainsafia marked this conversation as resolved.
Show resolved Hide resolved

Choose a reason for hiding this comment

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

This will fire when any debug session finishes -- you want to look at the session to make sure it's the one we care about. (e.g. I might have an API server written in some other language, stopping/restarting that shouldn't cause my blazor debugging to stop)

Copy link
Member Author

Choose a reason for hiding this comment

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

Seems sensible. The type property on the DebugSession seemed like the best thing to check against but let me know if there is a better approach here.

await onDidTerminateDebugSession(event, this.logger, await output.processId);
NTaylorMullen marked this conversation as resolved.
Show resolved Hide resolved
terminate.dispose();
});
captainsafia marked this conversation as resolved.
Show resolved Hide resolved

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

const browser = {
name: '.NET Core Debug Blazor Web Assembly in Browser',
type: configuration.browser === 'edge' ? 'pwa-msedge' : 'pwa-chrome',
request: 'launch',
timeout: configuration.timeout || 30000,
url: configuration.url || 'https://localhost:5001',
webRoot: configuration.webRoot || '${workspaceFolder}',
inspectUri: '{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}',
trace: configuration.trace || false,
noDebug: configuration.noDebug || false,
};
captainsafia marked this conversation as resolved.
Show resolved Hide resolved

try {
captainsafia marked this conversation as resolved.
Show resolved Hide resolved
/**
* The browser debugger will immediately launch after the
* application process is started. It waits a `timeout`
* interval before crashing after being unable to find the launched
* process.
*
* 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
} catch (error) {
this.logger.logError(
'[DEBUGGER] Error when launching browser debugger: ',
error,
);
const message = `There was an unexpected error while launching your debugging session. Check the console for helpful logs and visit the debugging docs for more info.`;
this.vscodeType.window.showErrorMessage(message, `View Debug Docs`, `Ignore`).then(async result => {
if (result === 'View Debug Docs') {
const debugDocsUri = 'https://aka.ms/blazorwasmcodedebug';
await this.vscodeType.commands.executeCommand(`vcode.open`, debugDocsUri);
}
});
}

/**
* 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
* the configuration to avoid a bug where VS Code is unable to resolve
* the debug adapter for the browser debugger.
*/
return undefined;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */

import * as psList from 'ps-list';
import { DebugSession } from 'vscode';

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

export async function onDidTerminateDebugSession(
event: DebugSession,
logger: RazorLogger,
targetPid: number | undefined,
) {
if (event.type !== 'pwa-chrome' && event.type !== 'pwa-msedge') {
return;
}
if (!targetPid) {
return;
}

logger.logVerbose(`[DEBUGGER] Terminating debugging session with PID ${targetPid}...`);

let processes: psList.ProcessDescriptor[] = [];
try {
processes = await psList();
} catch (error) {
logger.logError(`Error retrieving processes under PID ${targetPid} to clean-up: `, error);
}

try {
process.kill(targetPid);
processes.map((proc) => {

Choose a reason for hiding this comment

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

processes.map((proc) => { [](start = 4, length = 25)

Rather than using map I think you want to instead loop through processes to find targetPid. Then loop through just the remaining entries in the map to find any child processes. This way you will avoid the process id reuse problem with ppid.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll add this in a future PR.

if (proc.ppid === targetPid) {
process.kill(proc.pid);
}
});
logger.logVerbose(`[DEBUGGER] Debug process clean-up of PID ${targetPid} complete.`);
} catch (error) {
logger.logError(`[DEBUGGER] Error terminating debug processes with PID ${targetPid}: `, error);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import * as vscode from 'vscode';
import * as vscodeapi from 'vscode';
import { ExtensionContext } from 'vscode';
import { BlazorDebugConfigurationProvider } from './BlazorDebug/BlazorDebugConfigurationProvider';
import { CompositeCodeActionTranslator } from './CodeActions/CompositeRazorCodeActionTranslator';
import { RazorCodeActionProvider } from './CodeActions/RazorCodeActionProvider';
import { RazorFullyQualifiedCodeActionTranslator } from './CodeActions/RazorFullyQualifiedCodeActionTranslator';
Expand Down Expand Up @@ -182,6 +183,9 @@ export async function activate(vscodeType: typeof vscodeapi, context: ExtensionC
localRegistrations.length = 0;
});

const provider = new BlazorDebugConfigurationProvider(logger, vscodeType);
context.subscriptions.push(vscodeType.debug.registerDebugConfigurationProvider('blazorwasm', provider));
captainsafia marked this conversation as resolved.
Show resolved Hide resolved

languageServerClient.onStarted(async () => {
await documentManager.initialize();
});
Expand Down
5 changes: 5 additions & 0 deletions src/Razor/src/Microsoft.AspNetCore.Razor.VSCode/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ path-parse@^1.0.6:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==

ps-list@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/ps-list/-/ps-list-7.0.0.tgz#fd740a839786605d257117b899031db9b34b8b4b"
integrity sha512-ZDhdxqb+kE895BAvqIdGnWwfvB43h7KHMIcJC0hw7xLbbiJoprS+bqZxuGZ0jWdDxZEvB3jpnfgJyOn3lmsH+Q==

resolve@^1.3.2:
version "1.12.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2606,6 +2606,11 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.3"

ps-list@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/ps-list/-/ps-list-7.0.0.tgz#fd740a839786605d257117b899031db9b34b8b4b"
integrity sha512-ZDhdxqb+kE895BAvqIdGnWwfvB43h7KHMIcJC0hw7xLbbiJoprS+bqZxuGZ0jWdDxZEvB3jpnfgJyOn3lmsH+Q==

psl@^1.1.24, psl@^1.1.28:
version "1.7.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c"
Expand Down