Skip to content

Commit

Permalink
Add support for remote debugging with experimental debugger (#1230)
Browse files Browse the repository at this point in the history
* Fixes #1229
* Fixes #1265
  • Loading branch information
DonJayamanne authored Apr 4, 2018
1 parent 60ff772 commit 3e40ce2
Show file tree
Hide file tree
Showing 13 changed files with 677 additions and 78 deletions.
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": true, // 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
51 changes: 51 additions & 0 deletions src/client/debugger/DebugServers/RemoteDebugServerv2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import { connect, Socket } from 'net';
import { DebugSession } from 'vscode-debugadapter';
import { AttachRequestArguments, IDebugServer, IPythonProcess } from '../Common/Contracts';
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) => {
const port = this.args.port!;
const options = { port };
if (typeof this.args.host === 'string' && this.args.host.length > 0) {
// tslint:disable-next-line:no-any
(<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);
}
});
}
}
61 changes: 41 additions & 20 deletions src/client/debugger/configProviders/baseProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,55 @@ 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 {
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 numberOfSettings = Object.keys(config);
const workspaceFolder = this.getWorkspaceFolder(folder, config);
const workspaceFolder = this.getWorkspaceFolder(folder);

if ((config.noDebug === true && numberOfSettings.length === 1) || numberOfSettings.length === 0) {
const defaultProgram = this.getProgram(config);
if (debugConfiguration.request === 'attach') {
this.provideAttachDefaults(workspaceFolder, debugConfiguration as PythonAttachDebugConfiguration);
} else {
const config = debugConfiguration as PythonLaunchDebugConfiguration;
const numberOfSettings = Object.keys(config);

config.name = 'Launch';
config.type = this.debugType;
config.request = 'launch';
config.program = defaultProgram ? defaultProgram : '';
config.env = {};
}
if ((config.noDebug === true && numberOfSettings.length === 1) || numberOfSettings.length === 0) {
const defaultProgram = this.getProgram();

config.name = 'Launch';
config.type = this.debugType;
config.request = 'launch';
config.program = defaultProgram ? defaultProgram : '';
config.env = {};
}

this.provideDefaults(workspaceFolder, config);
return config;
this.provideLaunchDefaults(workspaceFolder, config);
}
return debugConfiguration;
}
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 provideDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonDebugConfiguration): void {
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,11 +96,11 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro
}
}
}
private getWorkspaceFolder(folder: WorkspaceFolder | undefined, config: PythonDebugConfiguration): Uri | undefined {
private getWorkspaceFolder(folder: WorkspaceFolder | undefined): Uri | undefined {
if (folder) {
return folder.uri;
}
const program = this.getProgram(config);
const program = this.getProgram();
const workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
if (!Array.isArray(workspaceService.workspaceFolders) || workspaceService.workspaceFolders.length === 0) {
return program ? Uri.file(path.dirname(program)) : undefined;
Expand All @@ -94,14 +115,14 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro
}
}
}
private getProgram(config: PythonDebugConfiguration): string | undefined {
private getProgram(): 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

0 comments on commit 3e40ce2

Please sign in to comment.