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

Expose API to get command for launching debugger in remote debug mode #3399

Merged
merged 3 commits into from
Nov 20, 2018
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions news/1 Enhancements/3121.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Expose an API that can be used by other extensions to interact with the Python Extension.
35 changes: 35 additions & 0 deletions src/client/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,41 @@

'use strict';

import { RemoteDebuggerLauncherScriptProvider } from './debugger/debugAdapter/DebugClients/launcherProvider';

/*
* Do not introduce any breaking changes to this API.
* This is the public API for other extensions to interact with this extension.
*/

export interface IExtensionApi {
/**
* Promise indicating whether all parts of the extension have completed loading or not.
* @type {Promise<void>}
* @memberof IExtensionApi
*/
ready: Promise<void>;
debug: {
/**
* Generate an array of strings for commands to pass to the Python executable to launch the debugger for remote debugging.
* Users can append another array of strings of what they want to execute along with relevant arguments to Python.
* E.g `['/Users/..../pythonVSCode/pythonFiles/experimental/ptvsd_launcher.py', '--host', 'localhost', '--port', '57039', '--wait']`
* @param {string} host
* @param {number} port
* @param {boolean} [waitUntilDebuggerAttaches=true]
* @returns {Promise<string[]>}
*/
getRemoteLauncherCommand(host: string, port: number, waitUntilDebuggerAttaches: boolean): Promise<string[]>;
};
}

export function buildApi(ready: Promise<void>) {
return {
ready,
debug: {
async getRemoteLauncherCommand(host: string, port: number, waitUntilDebuggerAttaches: boolean = true): Promise<string[]> {
return new RemoteDebuggerLauncherScriptProvider().getLauncherArgs({ host, port, waitUntilDebuggerAttaches });
}
}
};
}
4 changes: 2 additions & 2 deletions src/client/debugger/debugAdapter/DebugClients/DebugFactory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DebugSession } from 'vscode-debugadapter';
import { AttachRequestArguments, LaunchRequestArguments } from '../../types';
import { IDebugLauncherScriptProvider } from '../types';
import { ILocalDebugLauncherScriptProvider } from '../types';
import { DebugClient } from './DebugClient';
import { DebuggerLauncherScriptProvider, NoDebugLauncherScriptProvider } from './launcherProvider';
import { LocalDebugClient } from './LocalDebugClient';
Expand All @@ -9,7 +9,7 @@ import { NonDebugClientV2 } from './nonDebugClientV2';
import { RemoteDebugClient } from './RemoteDebugClient';

export function CreateLaunchDebugClient(launchRequestOptions: LaunchRequestArguments, debugSession: DebugSession, canLaunchTerminal: boolean): DebugClient<{}> {
let launchScriptProvider: IDebugLauncherScriptProvider;
let launchScriptProvider: ILocalDebugLauncherScriptProvider;
let debugClientClass: typeof LocalDebugClient;
if (launchRequestOptions.noDebug === true) {
launchScriptProvider = new NoDebugLauncherScriptProvider();
Expand Down
28 changes: 4 additions & 24 deletions src/client/debugger/debugAdapter/DebugClients/LocalDebugClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,15 @@ import { CurrentProcess } from '../../../common/process/currentProcess';
import { noop } from '../../../common/utils/misc';
import { EnvironmentVariablesService } from '../../../common/variables/environment';
import { IServiceContainer } from '../../../ioc/types';
import { DebugOptions, LaunchRequestArguments } from '../../types';
import { LaunchRequestArguments } from '../../types';
import { IDebugServer } from '../Common/Contracts';
import { IS_WINDOWS } from '../Common/Utils';
import { BaseDebugServer } from '../DebugServers/BaseDebugServer';
import { LocalDebugServerV2 } from '../DebugServers/LocalDebugServerV2';
import { IDebugLauncherScriptProvider } from '../types';
import { ILocalDebugLauncherScriptProvider } from '../types';
import { DebugClient, DebugType } from './DebugClient';
import { DebugClientHelper } from './helper';

const VALID_DEBUG_OPTIONS = [
'RedirectOutput',
'DebugStdLib',
'StopOnEntry',
'ShowReturnValue',
'BreakOnSystemExitZero',
'DjangoDebugging',
'Django'];

enum DebugServerStatus {
Unknown = 1,
Running = 2,
Expand All @@ -44,7 +35,7 @@ export class LocalDebugClient extends DebugClient<LaunchRequestArguments> {
}
return DebugServerStatus.Unknown;
}
constructor(args: LaunchRequestArguments, debugSession: DebugSession, private canLaunchTerminal: boolean, protected launcherScriptProvider: IDebugLauncherScriptProvider) {
constructor(args: LaunchRequestArguments, debugSession: DebugSession, private canLaunchTerminal: boolean, protected launcherScriptProvider: ILocalDebugLauncherScriptProvider) {
super(args, debugSession);
}

Expand Down Expand Up @@ -143,18 +134,7 @@ export class LocalDebugClient extends DebugClient<LaunchRequestArguments> {

// tslint:disable-next-line:member-ordering
protected buildDebugArguments(cwd: string, debugPort: number): string[] {
const ptVSToolsFilePath = this.launcherScriptProvider.getLauncherFilePath();
const vsDebugOptions: string[] = [DebugOptions.RedirectOutput];
if (Array.isArray(this.args.debugOptions)) {
this.args.debugOptions.filter(opt => VALID_DEBUG_OPTIONS.indexOf(opt) >= 0)
.forEach(item => vsDebugOptions.push(item));
}
const djangoIndex = vsDebugOptions.indexOf(DebugOptions.Django);
// PTVSD expects the string `DjangoDebugging`
if (djangoIndex >= 0) {
vsDebugOptions[djangoIndex] = 'DjangoDebugging';
}
return [ptVSToolsFilePath, cwd, debugPort.toString(), '34806ad9-833a-4524-8cd6-18ca4aa74f14', vsDebugOptions.join(',')];
throw new Error('Not Implemented');
}
// tslint:disable-next-line:member-ordering
protected buildStandardArguments() {
Expand Down
22 changes: 15 additions & 7 deletions src/client/debugger/debugAdapter/DebugClients/launcherProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,24 @@

import * as path from 'path';
import { EXTENSION_ROOT_DIR } from '../../../common/constants';
import { IDebugLauncherScriptProvider } from '../types';
import { IDebugLauncherScriptProvider, IRemoteDebugLauncherScriptProvider, LocalDebugOptions, RemoteDebugOptions } from '../types';

export class NoDebugLauncherScriptProvider implements IDebugLauncherScriptProvider {
public getLauncherFilePath(): string {
return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd_launcher.py');
const script = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd_launcher.py');
DonJayamanne marked this conversation as resolved.
Show resolved Hide resolved
export class NoDebugLauncherScriptProvider implements IDebugLauncherScriptProvider<LocalDebugOptions> {
public getLauncherArgs(options: LocalDebugOptions): string[] {
return [script, '--nodebug', '--client', '--host', options.host, '--port', options.port.toString()];
}
}

export class DebuggerLauncherScriptProvider implements IDebugLauncherScriptProvider {
public getLauncherFilePath(): string {
return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd_launcher.py');
export class DebuggerLauncherScriptProvider implements IDebugLauncherScriptProvider<LocalDebugOptions> {
public getLauncherArgs(options: LocalDebugOptions): string[] {
return [script, '--client', '--host', options.host, '--port', options.port.toString()];
}
}

export class RemoteDebuggerLauncherScriptProvider implements IRemoteDebugLauncherScriptProvider {
public getLauncherArgs(options: RemoteDebugOptions): string[] {
const waitArgs = options.waitUntilDebuggerAttaches ? ['--wait'] : [];
return [script, '--host', options.host, '--port', options.port.toString()].concat(waitArgs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,15 @@

import { DebugSession } from 'vscode-debugadapter';
import { LaunchRequestArguments } from '../../types';
import { IDebugLauncherScriptProvider } from '../types';
import { ILocalDebugLauncherScriptProvider } from '../types';
import { LocalDebugClient } from './LocalDebugClient';

export class LocalDebugClientV2 extends LocalDebugClient {
constructor(args: LaunchRequestArguments, debugSession: DebugSession, canLaunchTerminal: boolean, launcherScriptProvider: IDebugLauncherScriptProvider) {
constructor(args: LaunchRequestArguments, debugSession: DebugSession, canLaunchTerminal: boolean, launcherScriptProvider: ILocalDebugLauncherScriptProvider) {
super(args, debugSession, canLaunchTerminal, launcherScriptProvider);
}
protected buildDebugArguments(cwd: string, debugPort: number): string[] {
const launcher = this.launcherScriptProvider.getLauncherFilePath();
const additionalPtvsdArgs: string[] = [];
if (this.args.noDebug) {
additionalPtvsdArgs.push('--nodebug');
}
return [launcher, ...additionalPtvsdArgs, '--client', '--host', 'localhost', '--port', debugPort.toString()];
return this.launcherScriptProvider.getLauncherArgs({ host: 'localhost', port: debugPort });
}
protected buildStandardArguments() {
const programArgs = Array.isArray(this.args.args) && this.args.args.length > 0 ? this.args.args : [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
import { ChildProcess } from 'child_process';
import { DebugSession } from 'vscode-debugadapter';
import { LaunchRequestArguments } from '../../types';
import { IDebugLauncherScriptProvider } from '../types';
import { ILocalDebugLauncherScriptProvider } from '../types';
import { DebugType } from './DebugClient';
import { LocalDebugClientV2 } from './localDebugClientV2';

export class NonDebugClientV2 extends LocalDebugClientV2 {
constructor(args: LaunchRequestArguments, debugSession: DebugSession, canLaunchTerminal: boolean, launcherScriptProvider: IDebugLauncherScriptProvider) {
constructor(args: LaunchRequestArguments, debugSession: DebugSession, canLaunchTerminal: boolean, launcherScriptProvider: ILocalDebugLauncherScriptProvider) {
super(args, debugSession, canLaunchTerminal, launcherScriptProvider);
}

Expand Down
14 changes: 12 additions & 2 deletions src/client/debugger/debugAdapter/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,18 @@ import { Disposable } from 'vscode';
import { Logger } from 'vscode-debugadapter';
import { Message } from 'vscode-debugadapter/lib/messages';

export interface IDebugLauncherScriptProvider {
getLauncherFilePath(): string;
export type LocalDebugOptions = { port: number; host: string };
export type RemoteDebugOptions = LocalDebugOptions & { waitUntilDebuggerAttaches: boolean };

export interface IDebugLauncherScriptProvider<T> {
getLauncherArgs(options: T): string[];
}

export interface ILocalDebugLauncherScriptProvider extends IDebugLauncherScriptProvider<LocalDebugOptions> {
getLauncherArgs(options: LocalDebugOptions): string[];
}

export interface IRemoteDebugLauncherScriptProvider extends IDebugLauncherScriptProvider<RemoteDebugOptions> {
}

export const IProtocolParser = Symbol('IProtocolParser');
Expand Down
4 changes: 2 additions & 2 deletions src/client/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Container } from 'inversify';
import { CodeActionKind, debug, DebugConfigurationProvider, Disposable, ExtensionContext, extensions, IndentAction, languages, Memento, OutputChannel, window } from 'vscode';
import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry';
import { IExtensionActivationService } from './activation/types';
import { IExtensionApi } from './api';
import { buildApi, IExtensionApi } from './api';
import { registerTypes as appRegisterTypes } from './application/serviceRegistry';
import { IApplicationDiagnostics } from './application/types';
import { DebugService } from './common/application/debugService';
Expand Down Expand Up @@ -164,7 +164,7 @@ export async function activate(context: ExtensionContext): Promise<IExtensionApi
durations.endActivateTime = stopWatch.elapsedTime;
activationDeferred.resolve();

const api = { ready: activationDeferred.promise };
const api = buildApi(activationDeferred.promise);
// In test environment return the DI Container.
if (isTestExecution()) {
// tslint:disable-next-line:no-any
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import { expect } from 'chai';
import * as fs from 'fs-extra';
import * as path from 'path';
import { EXTENSION_ROOT_DIR } from '../../../../client/common/constants';
import { DebuggerLauncherScriptProvider, NoDebugLauncherScriptProvider, RemoteDebuggerLauncherScriptProvider } from '../../../../client/debugger/debugAdapter/DebugClients/launcherProvider';

const expectedPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd_launcher.py');

suite('Debugger - Launcher Script Provider', () => {
test('Ensure launcher script exists', async () => {
expect(await fs.pathExists(expectedPath)).to.be.deep.equal(true, 'Debugger launcher script does not exist');
});
test('Test debug launcher args', async () => {
const args = new DebuggerLauncherScriptProvider().getLauncherArgs({ host: 'something', port: 1234 });
const expectedArgs = [expectedPath, '--client', '--host', 'something', '--port', '1234'];
expect(args).to.be.deep.equal(expectedArgs);
});
test('Test non-debug launcher args', async () => {
const args = new NoDebugLauncherScriptProvider().getLauncherArgs({ host: 'something', port: 1234 });
const expectedArgs = [expectedPath, '--nodebug', '--client', '--host', 'something', '--port', '1234'];
expect(args).to.be.deep.equal(expectedArgs);
});
test('Test remote debug launcher args (and do not wait for debugger to attach)', async () => {
const args = new RemoteDebuggerLauncherScriptProvider().getLauncherArgs({ host: 'something', port: 1234, waitUntilDebuggerAttaches: false });
const expectedArgs = [expectedPath, '--host', 'something', '--port', '1234'];
expect(args).to.be.deep.equal(expectedArgs);
});
test('Test remote debug launcher args (and wait for debugger to attach)', async () => {
const args = new RemoteDebuggerLauncherScriptProvider().getLauncherArgs({ host: 'something', port: 1234, waitUntilDebuggerAttaches: true });
const expectedArgs = [expectedPath, '--host', 'something', '--port', '1234', '--wait'];
expect(args).to.be.deep.equal(expectedArgs);
});
});
20 changes: 0 additions & 20 deletions src/test/debugger/launcherScriptProvider.unit.test.ts

This file was deleted.

26 changes: 26 additions & 0 deletions src/test/extension.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

// tslint:disable:no-any

import { expect } from 'chai';
import * as path from 'path';
import { buildApi } from '../client/api';
import { EXTENSION_ROOT_DIR } from '../client/common/constants';

const expectedPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd_launcher.py');

suite('Extension API Debugger', () => {
test('Test debug launcher args (no-wait)', async () => {
const args = await buildApi(Promise.resolve()).debug.getRemoteLauncherCommand('something', 1234, false);
const expectedArgs = [expectedPath, '--host', 'something', '--port', '1234'];
expect(args).to.be.deep.equal(expectedArgs);
});
test('Test debug launcher args (wait)', async () => {
const args = await buildApi(Promise.resolve()).debug.getRemoteLauncherCommand('something', 1234, true);
const expectedArgs = [expectedPath, '--host', 'something', '--port', '1234', '--wait'];
expect(args).to.be.deep.equal(expectedArgs);
});
});