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 remote debugging with experimental debugger #1230

Merged
merged 20 commits into from
Apr 4, 2018
Merged
Show file tree
Hide file tree
Changes from 13 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
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"coverage": true
},
"typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
"tslint.enable": true,
"tslint.enable": false, // We will run our own linting in gulp (& git commit hooks), else tslint extension just complains about unmodified files
"python.linting.enabled": false,
"python.unitTest.promptToConfigure": false,
"python.workspaceSymbols.enabled": false,
Expand Down
2 changes: 2 additions & 0 deletions news/1 Enhancements/1229.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add prelimnary support for remote debugging using the experimental debugger.
Attach to a Python program started using the command `python -m ptvsd --server --port 9091 --file pythonFile.py`
69 changes: 69 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,19 @@
"Pyramid"
]
}
},
{
"label": "Python Experimental: Attach",
"description": "%python.snippet.launch.attach.description%",
"body": {
"name": "Attach (Remote Debug)",
"type": "pythonExperimental",
"request": "attach",
"localRoot": "^\"\\${workspaceFolder}\"",
"remoteRoot": "^\"\\${workspaceFolder}\"",
"port": 3000,
"host": "localhost"
}
}
],
"configurationAttributes": {
Expand Down Expand Up @@ -963,6 +976,53 @@
"default": false
}
}
},
"attach": {
"required": [
"port",
"remoteRoot"
],
"properties": {
"localRoot": {
"type": "string",
"description": "Local source root that corrresponds to the 'remoteRoot'.",
"default": "${workspaceFolder}"
},
"remoteRoot": {
"type": "string",
"description": "The source root of the remote host.",
"default": ""
},
"port": {
"type": "number",
"description": "Debug port to attach",
"default": 0
},
"host": {
"type": "string",
"description": "IP Address of the of remote server (default is localhost or use 127.0.0.1).",
"default": "localhost"
},
"debugOptions": {
"type": "array",
"description": "Advanced options, view read me for further details.",
"items": {
"type": "string",
"enum": [
"RedirectOutput",
"DebugStdLib",
"Django",
"Jinja"
]
},
"default": []
},
"logToFile": {
"type": "boolean",
"description": "Enable logging of debugger events to a log file.",
"default": false
}
}
}
},
"initialConfigurations": [
Expand All @@ -973,6 +1033,15 @@
"program": "${file}",
"console": "integratedTerminal"
},
{
"name": "Python Experimental: Attach",
"type": "pythonExperimental",
"request": "pythonExperimental",
"localRoot": "${workspaceFolder}",
"remoteRoot": "${workspaceFolder}",
"port": 3000,
"host": "localhost"
},
{
"name": "Python Experimental: Django",
"type": "pythonExperimental",
Expand Down
1 change: 1 addition & 0 deletions src/client/debugger/Common/Contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum
}

export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments {
type?: DebuggerType;
/** An absolute path to local directory with source. */
localRoot: string;
remoteRoot: string;
Expand Down
14 changes: 10 additions & 4 deletions src/client/debugger/DebugClients/RemoteDebugClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@ import { DebugSession } from 'vscode-debugadapter';
import { AttachRequestArguments, IPythonProcess } from '../Common/Contracts';
import { BaseDebugServer } from '../DebugServers/BaseDebugServer';
import { RemoteDebugServer } from '../DebugServers/RemoteDebugServer';
import { RemoteDebugServerV2 } from '../DebugServers/RemoteDebugServerv2';
import { DebugClient, DebugType } from './DebugClient';

export class RemoteDebugClient extends DebugClient<AttachRequestArguments> {
private pythonProcess: IPythonProcess;
private pythonProcess?: IPythonProcess;
private debugServer?: BaseDebugServer;
// tslint:disable-next-line:no-any
constructor(args: any, debugSession: DebugSession) {
constructor(args: AttachRequestArguments, debugSession: DebugSession) {
super(args, debugSession);
}

public CreateDebugServer(pythonProcess?: IPythonProcess): BaseDebugServer {
this.pythonProcess = pythonProcess!;
this.debugServer = new RemoteDebugServer(this.debugSession, this.pythonProcess!, this.args);
if (this.args.type === 'pythonExperimental') {
// tslint:disable-next-line:no-any
this.debugServer = new RemoteDebugServerV2(this.debugSession, undefined as any, this.args);
} else {
this.pythonProcess = pythonProcess!;
this.debugServer = new RemoteDebugServer(this.debugSession, this.pythonProcess!, this.args);
}
return this.debugServer!;
}
public get DebugType(): DebugType {
Expand Down
52 changes: 52 additions & 0 deletions src/client/debugger/DebugServers/RemoteDebugServerv2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

// tslint:disable:quotemark ordered-imports no-any no-empty curly member-ordering one-line max-func-body-length no-var-self prefer-const cyclomatic-complexity prefer-template

import { DebugSession } from "vscode-debugadapter";
import { IPythonProcess, IDebugServer, AttachRequestArguments } from "../Common/Contracts";

Choose a reason for hiding this comment

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

Evil double quotes?

Copy link
Author

Choose a reason for hiding this comment

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

aaah, copy paste.

import { connect, Socket } from "net";
import { BaseDebugServer } from "./BaseDebugServer";

export class RemoteDebugServerV2 extends BaseDebugServer {
private args: AttachRequestArguments;
private socket?: Socket;
constructor(debugSession: DebugSession, pythonProcess: IPythonProcess, args: AttachRequestArguments) {
super(debugSession, pythonProcess);
this.args = args;
}

public Stop() {
if (this.socket) {
this.socket.destroy();
}
}
public Start(): Promise<IDebugServer> {
return new Promise<IDebugServer>((resolve, reject) => {
let portNumber = this.args.port;
let options = { port: portNumber! };
if (typeof this.args.host === "string" && this.args.host.length > 0) {
(<any>options).host = this.args.host;
}
try {
let connected = false;
const socket = connect(options, () => {
connected = true;
this.socket = socket;
this.clientSocket.resolve(socket);
resolve(options);
});
socket.on('error', ex => {
if (connected) {
return;
}
reject(ex);
});
} catch (ex) {
reject(ex);
}
});
}
}
37 changes: 29 additions & 8 deletions src/client/debugger/configProviders/baseProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ import { PythonLanguage } from '../../common/constants';
import { IFileSystem, IPlatformService } from '../../common/platform/types';
import { IConfigurationService } from '../../common/types';
import { IServiceContainer } from '../../ioc/types';
import { DebuggerType, DebugOptions, LaunchRequestArguments } from '../Common/Contracts';
import { AttachRequestArguments, DebuggerType, DebugOptions, LaunchRequestArguments } from '../Common/Contracts';

// tslint:disable:no-invalid-template-strings

export type PythonDebugConfiguration = DebugConfiguration & LaunchRequestArguments;
export type PythonLaunchDebugConfiguration = DebugConfiguration & LaunchRequestArguments;
export type PythonAttachDebugConfiguration = DebugConfiguration & AttachRequestArguments;

@injectable()
export abstract class BaseConfigurationProvider implements DebugConfigurationProvider {

Choose a reason for hiding this comment

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

Might be worth splitting into AttachConfigurationProvider and LaunchConfigurationProvider and have static factory like getConfigurationProvider(mode). Class has bunch of getLaunchX and getAttachX and casts and checks for the debug mode.

Copy link
Author

Choose a reason for hiding this comment

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

Unfortunately there can only be one debug configuration, that handles both launch and attach (& more in the future).
I've cleaned up the code,

Choose a reason for hiding this comment

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

There could be one class that contains a handler specific to the mode. I.e. debug configuration class acts as a proxy and simply forwards calls to 'launch or 'attach handler

Copy link
Author

Choose a reason for hiding this comment

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

👍

constructor(@unmanaged() public debugType: DebuggerType, protected serviceContainer: IServiceContainer) { }
public resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult<DebugConfiguration> {
const config = debugConfiguration as PythonDebugConfiguration;
const config = debugConfiguration as PythonLaunchDebugConfiguration;
const numberOfSettings = Object.keys(config);
const workspaceFolder = this.getWorkspaceFolder(folder, config);

Expand All @@ -35,10 +36,30 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro
config.env = {};
}

this.provideDefaults(workspaceFolder, config);
if (config.request === 'attach') {
// tslint:disable-next-line:no-any
this.provideAttachDefaults(workspaceFolder, config as any as PythonAttachDebugConfiguration);

Choose a reason for hiding this comment

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

Interesting - why is getting casted twice

Copy link
Author

Choose a reason for hiding this comment

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

config can be of two types. Modified code accordingly to make it clearer (using a union type).

} else {
this.provideLaunchDefaults(workspaceFolder, config);
}
return config;
}
protected provideDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonDebugConfiguration): void {
protected provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): void {
if (!Array.isArray(debugConfiguration.debugOptions)) {
debugConfiguration.debugOptions = [];
}
// Always redirect output.
if (debugConfiguration.debugOptions.indexOf(DebugOptions.RedirectOutput) === -1) {
debugConfiguration.debugOptions.push(DebugOptions.RedirectOutput);
}
if (!debugConfiguration.host) {
debugConfiguration.host = 'localhost';
}
if (!debugConfiguration.localRoot && workspaceFolder) {
debugConfiguration.localRoot = workspaceFolder.fsPath;
}
}
protected provideLaunchDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void {
this.resolveAndUpdatePythonPath(workspaceFolder, debugConfiguration);
if (typeof debugConfiguration.cwd !== 'string' && workspaceFolder) {
debugConfiguration.cwd = workspaceFolder.fsPath;
Expand Down Expand Up @@ -75,7 +96,7 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro
}
}
}
private getWorkspaceFolder(folder: WorkspaceFolder | undefined, config: PythonDebugConfiguration): Uri | undefined {
private getWorkspaceFolder(folder: WorkspaceFolder | undefined, config: PythonLaunchDebugConfiguration): Uri | undefined {
if (folder) {
return folder.uri;
}
Expand All @@ -94,14 +115,14 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro
}
}
}
private getProgram(config: PythonDebugConfiguration): string | undefined {
private getProgram(config: PythonLaunchDebugConfiguration): string | undefined {
const documentManager = this.serviceContainer.get<IDocumentManager>(IDocumentManager);
const editor = documentManager.activeTextEditor;
if (editor && editor.document.languageId === PythonLanguage.language) {
return editor.document.fileName;
}
}
private resolveAndUpdatePythonPath(workspaceFolder: Uri | undefined, debugConfiguration: PythonDebugConfiguration): void {
private resolveAndUpdatePythonPath(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void {
if (!debugConfiguration) {
return;
}
Expand Down
16 changes: 13 additions & 3 deletions src/client/debugger/configProviders/pythonV2Provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import { Uri } from 'vscode';
import { IPlatformService } from '../../common/platform/types';
import { IServiceContainer } from '../../ioc/types';
import { DebugOptions } from '../Common/Contracts';
import { BaseConfigurationProvider, PythonDebugConfiguration } from './baseProvider';
import { BaseConfigurationProvider, PythonAttachDebugConfiguration, PythonLaunchDebugConfiguration } from './baseProvider';

@injectable()
export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvider {
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
super('pythonExperimental', serviceContainer);
}
protected provideDefaults(workspaceFolder: Uri, debugConfiguration: PythonDebugConfiguration): void {
super.provideDefaults(workspaceFolder, debugConfiguration);
protected provideLaunchDefaults(workspaceFolder: Uri, debugConfiguration: PythonLaunchDebugConfiguration): void {
super.provideLaunchDefaults(workspaceFolder, debugConfiguration);

debugConfiguration.stopOnEntry = false;
debugConfiguration.debugOptions = Array.isArray(debugConfiguration.debugOptions) ? debugConfiguration.debugOptions : [];
Expand All @@ -30,4 +30,14 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide
debugConfiguration.debugOptions.push(DebugOptions.Jinja);
}
}
protected provideAttachDefaults(workspaceFolder: Uri, debugConfiguration: PythonAttachDebugConfiguration): void {
super.provideAttachDefaults(workspaceFolder, debugConfiguration);

debugConfiguration.debugOptions = Array.isArray(debugConfiguration.debugOptions) ? debugConfiguration.debugOptions : [];

// Add PTVSD specific flags.
if (this.serviceContainer.get<IPlatformService>(IPlatformService).isWindows) {
debugConfiguration.debugOptions.push(DebugOptions.FixFilePathCase);
}
}
}
Loading