Skip to content

Commit

Permalink
#3062 add ability to debug backend plugins in hosted mode
Browse files Browse the repository at this point in the history
Signed-off-by: Yevhen Vydolob <yvydolob@redhat.com>
  • Loading branch information
evidolob committed Oct 8, 2018
1 parent cc28704 commit 8c3be7c
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 37 deletions.
13 changes: 7 additions & 6 deletions packages/debug-nodejs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"description": "Theia - NodeJS Debug Extension",
"dependencies": {
"@theia/debug": "^0.3.15",
"vscode-debugprotocol": "^1.26.0"
"vscode-debugprotocol": "^1.26.0",
"unzip-stream": "^0.3.0"
},
"publishConfig": {
"access": "public"
Expand All @@ -27,22 +28,22 @@
},
"homepage": "https://github.com/theia-ide/theia",
"files": [
"lib",
"lib/node",
"src",
"scripts"
],
"scripts": {
"prepare": "yarn run clean && yarn run build",
"clean": "theiaext clean",
"download": "node ./scripts/download-vscode-node-debug.js",
"build": "yarn download && theiaext build",
"build": "yarn postinstall && theiaext build",
"watch": "theiaext watch",
"test": "theiaext test",
"docs": "theiaext docs"
"docs": "theiaext docs",
"postinstall": "node ./scripts/download-vscode-node-debug.js"
},
"devDependencies": {
"@theia/ext-scripts": "^0.3.15",
"unzip-stream": "^0.3.0"
"@theia/ext-scripts": "^0.3.15"
},
"nyc": {
"extends": "../../configs/nyc.json"
Expand Down
10 changes: 0 additions & 10 deletions packages/debug-nodejs/src/node/debug-nodejs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ export class NodeJsDebugAdapterContribution implements DebugAdapterContribution
throw new Error('Debug request type is not provided.');
}

switch (config.request) {
case 'attach': this.validateAttachConfig(config);
}

return config;
}

Expand All @@ -58,10 +54,4 @@ export class NodeJsDebugAdapterContribution implements DebugAdapterContribution
runtime: 'node'
};
}

private validateAttachConfig(config: DebugConfiguration) {
if (!config.processId) {
throw new Error('PID is not provided.');
}
}
}
1 change: 1 addition & 0 deletions packages/plugin-ext/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@theia/navigator": "^0.3.15",
"@theia/plugin": "^0.3.15",
"@theia/workspace": "^0.3.15",
"@theia/debug-nodejs": "^0.3.15",
"decompress": "^4.2.0",
"lodash.clonedeep": "^4.5.0",
"ps-tree": "1.1.0",
Expand Down
22 changes: 14 additions & 8 deletions packages/plugin-ext/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ export interface PluginPackageViewContainer {
}

export interface PluginPackageView {
id: string;
name: string;
id: string;
name: string;
}

export interface PluginPackageMenu {
command: string;
group?: string;
command: string;
group?: string;
}

export interface PluginPackageGrammarsContribution {
Expand Down Expand Up @@ -372,16 +372,16 @@ export interface ViewContainer {
* View contribution
*/
export interface View {
id: string;
name: string;
id: string;
name: string;
}

/**
* Menu contribution
*/
export interface Menu {
command: string;
group?: string;
command: string;
group?: string;
}

/**
Expand Down Expand Up @@ -437,6 +437,11 @@ export function buildFrontendModuleName(plugin: PluginPackage | PluginModel): st
return `${plugin.publisher}_${plugin.name}`.replace(/\W/g, '_');
}

export interface DebugConfiguration {
port?: number;
debugMode?: string;
}

export const HostedPluginClient = Symbol('HostedPluginClient');
export interface HostedPluginClient {
postMessage(message: string): Promise<void>;
Expand All @@ -458,6 +463,7 @@ export interface HostedPluginServer extends JsonRpcServer<HostedPluginClient> {

isPluginValid(uri: string): Promise<boolean>;
runHostedPluginInstance(uri: string): Promise<string>;
runDebugHostedPluginInstance(uri: string, debugConfig: DebugConfiguration): Promise<string>;
terminateHostedPluginInstance(): Promise<void>;
isHostedPluginInstanceRunning(): Promise<boolean>;
getHostedPluginInstanceURI(): Promise<string>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import { WorkspaceService } from '@theia/workspace/lib/browser';
import { FileSystem } from '@theia/filesystem/lib/common';
import { OpenFileDialogFactory, DirNode } from '@theia/filesystem/lib/browser';
import { HostedPluginServer } from '../../common/plugin-protocol';
import { DebugService, DebugConfiguration } from '@theia/debug/lib/common/debug-common';
import { DebugConfiguration as HostedDebugConfig } from '../../common';
import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session';
import { HostedPluginPreferences } from './hosted-plugin-preferences';

/**
* Commands to control Hosted plugin instances.
Expand All @@ -32,6 +36,12 @@ export namespace HostedPluginCommands {
id: 'hosted-plugin:start',
label: 'Hosted Plugin: Start Instance'
};

export const DEBUG: Command = {
id: 'hosted-plugin:debug',
label: 'Hosted Plugin: Debug Instance'
};

export const STOP: Command = {
id: 'hosted-plugin:stop',
label: 'Hosted Plugin: Stop Instance'
Expand Down Expand Up @@ -75,6 +85,8 @@ export class HostedPluginManagerClient {
// URL to the running plugin instance
protected pluginInstanceURL: string | undefined;

protected isDebug = false;

protected readonly stateChanged = new Emitter<HostedInstanceData>();

get onStateChanged(): Event<HostedInstanceData> {
Expand All @@ -95,6 +107,12 @@ export class HostedPluginManagerClient {
protected readonly fileSystem: FileSystem;
@inject(WorkspaceService)
protected readonly workspaceService: WorkspaceService;
@inject(DebugService)
protected readonly debugService: DebugService;
@inject(DebugSessionManager)
protected readonly debugSessionManager: DebugSessionManager;
@inject(HostedPluginPreferences)
protected readonly hostedPluginPreferences: HostedPluginPreferences;

@postConstruct()
protected async init() {
Expand All @@ -113,7 +131,7 @@ export class HostedPluginManagerClient {
return undefined;
}

async start(): Promise<void> {
async start(debugConfig?: HostedDebugConfig): Promise<void> {
if (await this.hostedPluginServer.isHostedPluginInstanceRunning()) {
this.messageService.warn('Hosted instance is already running.');
return;
Expand All @@ -131,7 +149,11 @@ export class HostedPluginManagerClient {
this.stateChanged.fire({ state: HostedInstanceState.STARTING, pluginLocation: this.pluginLocation });
this.messageService.info('Starting hosted instance server ...');

this.pluginInstanceURL = await this.hostedPluginServer.runHostedPluginInstance(this.pluginLocation.toString());
if (debugConfig) {
this.pluginInstanceURL = await this.hostedPluginServer.runDebugHostedPluginInstance(this.pluginLocation.toString(), debugConfig);
} else {
this.pluginInstanceURL = await this.hostedPluginServer.runHostedPluginInstance(this.pluginLocation.toString());
}
await this.openPluginWindow();

this.messageService.info('Hosted instance is running at: ' + this.pluginInstanceURL);
Expand All @@ -142,6 +164,19 @@ export class HostedPluginManagerClient {
}
}

async debug(): Promise<void> {
this.isDebug = true;

await this.start({ debugMode: this.hostedPluginPreferences['hosted-plugin.debugMode'] });
const configuration = {
type: 'node',
request: 'attach',
name: 'Hosted Plugin'
} as DebugConfiguration;
const session = await this.debugService.create(configuration);
await this.debugSessionManager.create(session, configuration);
}

async stop(checkRunning: boolean = true): Promise<void> {
if (checkRunning && ! await this.hostedPluginServer.isHostedPluginInstanceRunning()) {
this.messageService.warn('Hosted instance is not running.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,19 @@ export const HostedPluginConfigSchema: PreferenceSchema = {
type: 'boolean',
description: 'Run watcher on plugin under development',
default: true
},
'hosted-plugin.debugMode': {
type: 'string',
description: 'Using inspect or inspect-brk for Node.js debug',
default: 'inspect',
enum: ['inspect', 'inspect-brk']
}
}
};

export interface HostedPluginConfiguration {
'hosted-plugin.watchMode': boolean;
'hosted-plugin.debugMode': string;
}

export const HostedPluginPreferences = Symbol('HostedPluginPreferences');
Expand Down
41 changes: 37 additions & 4 deletions packages/plugin-ext/src/hosted/node/hosted-instance-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ContributionProvider } from '@theia/core/lib/common/contribution-provid
import { LogType } from './../../common/types';
import { HostedPluginUriPostProcessor, HostedPluginUriPostProcessorSymbolName } from './hosted-plugin-uri-postprocessor';
import { HostedPluginSupport } from './hosted-plugin';
import { DebugConfiguration } from '../../common';
const processTree = require('ps-tree');

export const HostedInstanceManager = Symbol('HostedInstanceManager');
Expand All @@ -45,6 +46,14 @@ export interface HostedInstanceManager {
*/
run(pluginUri: URI, port?: number): Promise<URI>;

/**
* Runs specified by the given uri plugin with debug in separate Theia instance.
* @param pluginUri uri to the plugin source location
* @param debugConfig debug configuration
* @returns uri where new Theia instance is run
*/
debug(pluginUri: URI, debugConfig: DebugConfiguration): Promise<URI>;

/**
* Terminates hosted plugin instance.
* Throws error if instance is not running.
Expand Down Expand Up @@ -95,6 +104,14 @@ export abstract class AbstractHostedInstanceManager implements HostedInstanceMan
}

async run(pluginUri: URI, port?: number): Promise<URI> {
return this.doRun(pluginUri, port);
}

async debug(pluginUri: URI, debugConfig: DebugConfiguration): Promise<URI> {
return this.doRun(pluginUri, undefined, debugConfig);
}

private async doRun(pluginUri: URI, port?: number, debugConfig?: DebugConfiguration): Promise<URI> {
if (this.isPluginRunnig) {
this.hostedPluginSupport.sendLog({ data: 'Hosted plugin instance is already running.', type: LogType.Info });
throw new Error('Hosted instance is already running.');
Expand All @@ -108,7 +125,7 @@ export abstract class AbstractHostedInstanceManager implements HostedInstanceMan

// Disable all the other plugins on this instance
processOptions.env.THEIA_PLUGINS = '';
command = await this.getStartCommand(port);
command = await this.getStartCommand(port, debugConfig);
} else {
throw new Error('Not supported plugin location: ' + pluginUri.toString());
}
Expand Down Expand Up @@ -158,7 +175,7 @@ export abstract class AbstractHostedInstanceManager implements HostedInstanceMan
return false;
}

protected async getStartCommand(port?: number): Promise<string[]> {
protected async getStartCommand(port?: number, debugConfig?: DebugConfiguration): Promise<string[]> {
const command = ['yarn', 'theia', 'start'];
if (process.env.HOSTED_PLUGIN_HOSTNAME) {
command.push('--hostname=' + process.env.HOSTED_PLUGIN_HOSTNAME);
Expand All @@ -167,6 +184,22 @@ export abstract class AbstractHostedInstanceManager implements HostedInstanceMan
await this.validatePort(port);
command.push('--port=' + port);
}

if (debugConfig) {
let debugString = '--hosted-plugin-';
if (debugConfig.debugMode) {
debugString += debugConfig.debugMode;
} else {
debugString += 'inspect';
}

debugString += '=0.0.0.0';

if (debugConfig.port) {
debugString += ':' + debugConfig.port;
}
command.push(debugString);
}
return command;
}

Expand Down Expand Up @@ -248,15 +281,15 @@ export class NodeHostedPluginRunner extends AbstractHostedInstanceManager {
return uri;
}

protected async getStartCommand(port?: number): Promise<string[]> {
protected async getStartCommand(port?: number, config?: DebugConfiguration): Promise<string[]> {
if (!port) {
if (process.env.HOSTED_PLUGIN_PORT) {
port = Number(process.env.HOSTED_PLUGIN_PORT);
} else {
port = 3030;
}
}
return super.getStartCommand(port);
return super.getStartCommand(port, config);
}

}
Expand Down
15 changes: 9 additions & 6 deletions packages/plugin-ext/src/hosted/node/hosted-plugin-process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,28 +63,31 @@ export class HostedPluginProcess implements ServerPluginRunner {
}

public terminatePluginServer(): void {
const emitter = new Emitter();
if (this.childProcess === undefined) {
return;
}
// tslint:disable-next-line:no-shadowed-variable
const cp = this.childProcess;
this.childProcess = undefined;

this.childProcess.on('message', message => {
const emitter = new Emitter();
cp.on('message', message => {
emitter.fire(JSON.parse(message));
});
const rpc = new RPCProtocolImpl({
onMessage: emitter.event,
send: (m: {}) => {
if (this.childProcess!.send) {
this.childProcess!.send(JSON.stringify(m));
if (cp.send) {
cp.send(JSON.stringify(m));
}
}
});
const hostedPluginManager = rpc.getProxy(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT);
hostedPluginManager.$stopPlugin('').then(() => {
emitter.dispose();
this.childProcess!.kill();
cp.kill();
});
this.childProcess = undefined;

}

public runPluginServer(): void {
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-ext/src/hosted/node/hosted-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export class HostedPluginSupport {
clientClosed(): void {
this.isPluginProcessRunning = false;
this.terminatePluginServer();
this.isPluginProcessRunning = false;
}

runPlugin(plugin: PluginModel): void {
Expand Down
6 changes: 5 additions & 1 deletion packages/plugin-ext/src/hosted/node/plugin-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { injectable, inject } from 'inversify';
import { HostedPluginServer, HostedPluginClient, PluginMetadata, PluginDeployerEntry } from '../../common/plugin-protocol';
import { HostedPluginServer, HostedPluginClient, PluginMetadata, PluginDeployerEntry, DebugConfiguration } from '../../common/plugin-protocol';
import { HostedPluginReader } from './plugin-reader';
import { HostedInstanceManager } from './hosted-instance-manager';
import { HostedPluginSupport } from './hosted-plugin';
Expand Down Expand Up @@ -118,6 +118,10 @@ export class HostedPluginServerImpl implements HostedPluginServer {
return this.uriToStrPromise(this.hostedInstanceManager.run(new URI(uri)));
}

runDebugHostedPluginInstance(uri: string, debugConfig: DebugConfiguration): Promise<string> {
return this.uriToStrPromise(this.hostedInstanceManager.debug(new URI(uri), debugConfig));
}

terminateHostedPluginInstance(): Promise<void> {
return Promise.resolve(this.hostedInstanceManager.terminate());
}
Expand Down
Loading

0 comments on commit 8c3be7c

Please sign in to comment.