Skip to content
This repository has been archived by the owner on Jun 20, 2018. It is now read-only.

Commit

Permalink
Theia plugin metadata (#1)
Browse files Browse the repository at this point in the history
this PR implements
- retrieving metadata from plugins and using the metadata for plugin activation and deactivation;
- pluggability, which allows extending the plugin system to run different types of plugins.
  • Loading branch information
akurinnoy authored and benoitf committed Jun 4, 2018
1 parent bc39958 commit d2c54ff
Show file tree
Hide file tree
Showing 16 changed files with 496 additions and 106 deletions.
9 changes: 5 additions & 4 deletions packages/plugin/src/api/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@
*/
import { createProxyIdentifier, ProxyIdentifier } from './rpc-protocol';
import * as theia from '@theia/plugin';
import { PluginLifecycle, PluginModel } from '../common/plugin-protocol';

export interface HostedPluginManagerExt {
$loadPlugin(ext: Plugin): void;
$initialize(contextPath: string): void;
$loadPlugin(plugin: Plugin): void;
$stopPlugin(): PromiseLike<void>;
}

export interface Plugin {
name: string;
publisher: string;
version: string;
pluginPath: string;
model: PluginModel;
lifecycle: PluginLifecycle;
}

export interface CommandRegistryMain {
Expand Down
48 changes: 27 additions & 21 deletions packages/plugin/src/browser/hosted-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
import { injectable, inject, interfaces } from 'inversify';
import { HostedPluginServer, Plugin } from '../common/plugin-protocol';
import { HostedPluginServer, PluginModel, PluginLifecycle } from '../common/plugin-protocol';
import { PluginWorker } from './plugin-worker';
import { setUpPluginApi } from './main-context';
import { MAIN_RPC_CONTEXT } from '../api/plugin-api';
import { MAIN_RPC_CONTEXT, Plugin } from '../api/plugin-api';
import { HostedPluginWatcher } from './hosted-plugin-watcher';
import { RPCProtocol, RPCProtocolImpl } from '../api/rpc-protocol';
@injectable()
Expand All @@ -20,36 +20,42 @@ export class HostedPluginSupport {
}

checkAndLoadPlugin(container: interfaces.Container): void {
this.server.getHostedPlugin().then(plugin => {
if (plugin) {
this.loadPlugin(plugin, container);
this.server.getHostedPlugin().then((pluginMedata: any) => {
if (pluginMedata) {
this.loadPlugin(pluginMedata.model, pluginMedata.lifecycle, container);
}
});
}

private loadPlugin(plugin: Plugin, container: interfaces.Container): void {
if (plugin.theiaPlugin!.worker) {
console.log(`Loading hosted plugin: ${plugin.name}`);
private loadPlugin(pluginModel: PluginModel, pluginLifecycle: PluginLifecycle, container: interfaces.Container): void {
if (pluginModel.entryPoint!.frontend) {
console.log(`Loading hosted plugin: ${pluginModel.name}`);
this.worker = new PluginWorker();
setUpPluginApi(this.worker.rpc, container);
const hostedExtManager = this.worker.rpc.getProxy(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT);
hostedExtManager.$loadPlugin({
pluginPath: plugin.theiaPlugin.worker!,
name: plugin.name,
publisher: plugin.publisher,
version: plugin.version
});
const plugin: Plugin = {
pluginPath: pluginModel.entryPoint.frontend!,
model: pluginModel,
lifecycle: pluginLifecycle
};
if (pluginLifecycle.frontendInitPath) {
hostedExtManager.$initialize(pluginLifecycle.frontendInitPath);
}
hostedExtManager.$loadPlugin(plugin);
}
if (plugin.theiaPlugin!.node) {
if (pluginModel.entryPoint!.backend) {
const rpc = this.createServerRpc();
setUpPluginApi(rpc, container);
const hostedExtManager = rpc.getProxy(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT);
hostedExtManager.$loadPlugin({
pluginPath: plugin.theiaPlugin.node!,
name: plugin.name,
publisher: plugin.publisher,
version: plugin.version
});
const plugin: Plugin = {
pluginPath: pluginModel.entryPoint.backend!,
model: pluginModel,
lifecycle: pluginLifecycle
};
if (pluginLifecycle.backendInitPath) {
hostedExtManager.$initialize(pluginLifecycle.backendInitPath);
}
hostedExtManager.$loadPlugin(plugin);
}
}

Expand Down
120 changes: 117 additions & 3 deletions packages/plugin/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,128 @@
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
import { RPCProtocol } from '../api/rpc-protocol';
import { Disposable } from '../plugin/types-impl';

export const hostedServicePath = '/services/hostedPlugin';

export interface Plugin {
/**
* Plugin engine (API) type, i.e. 'theiaPlugin', 'vscode', etc.
*/
export type PluginEngine = string;

/**
* This interface describes a package.json object.
*/
export interface PluginPackage {
name: string;
publisher: string;
version: string;
engines: {
[type in PluginEngine]: string;
};
theiaPlugin?: {
frontend?: string;
backend?: string;
};
main?: string;
displayName: string;
description: string;
contributes: {};
}

export const PluginScanner = Symbol('PluginScanner');

/**
* This scanner process package.json object and returns plugin metadata objects.
*/
export interface PluginScanner {
/**
* The type of plugin's API (engine name)
*/
apiType: PluginEngine;

/**
* Creates plugin's model.
*
* @param {PluginPackage} plugin
* @returns {PluginModel}
*/
getModel(plugin: PluginPackage): PluginModel;

/**
* Creates plugin's lifecycle.
*
* @returns {PluginLifecycle}
*/
getLifecycle(plugin: PluginPackage): PluginLifecycle;
}

/**
* This interface describes a plugin model object, which is populated from package.json.
*/
export interface PluginModel {
name: string;
publisher: string;
version: string;
theiaPlugin: { worker?: string, node?: string };
displayName: string;
description: string;
engine: {
type: PluginEngine;
version: string;
};
entryPoint: {
frontend?: string;
backend?: string;
};
}

/**
* This interface describes a plugin lifecycle object.
*/
export interface PluginLifecycle {
startMethod: string;
stopMethod: string;
/**
* Frontend module name, frontend plugin should expose this name.
*/
frontendModuleName?: string;
/**
* Path to the script which should do some initialization before frontend plugin is loaded.
*/
frontendInitPath?: string;
/**
* Path to the script which should do some initialization before backend plugin is loaded.
*/
backendInitPath?: string;
}

/**
* The export function of initialization module of backend plugin.
*/
export interface BackendInitializationFn {
(rpc: RPCProtocol): void;
}

export interface PluginContext {
subscriptions: Disposable[];
}

export interface ExtensionContext {
subscriptions: Disposable[];
}

export interface PluginMetadata {
model: PluginModel;
lifecycle: PluginLifecycle;
}

export function getPluginId(plugin: PluginPackage | PluginModel): string {
return `${plugin.publisher}_${plugin.name}`;
}

export function buildFrontendModuleName(plugin: PluginPackage | PluginModel): string {
return `${plugin.publisher}_${plugin.name}`.replace(/\W/g, '_');
}

export const HostedPluginClient = Symbol('HostedPluginClient');
Expand All @@ -22,6 +136,6 @@ export interface HostedPluginClient {

export const HostedPluginServer = Symbol('HostedPluginServer');
export interface HostedPluginServer extends JsonRpcServer<HostedPluginClient> {
getHostedPlugin(): Promise<Plugin | undefined>;
getHostedPlugin(): Promise<PluginMetadata | undefined>;
onMessage(message: string): Promise<void>;
}
46 changes: 46 additions & 0 deletions packages/plugin/src/node/context/backend-init-theia.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2015-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/

import { BackendInitializationFn } from '../../common/plugin-protocol';
import { createAPI } from '../../plugin/plugin-context';

export const doInitialization: BackendInitializationFn = (rpc: any) => {
const theia = createAPI(rpc);

// add theia into global goal
const g = global as any;
g['theia'] = theia;

const NODE_MODULE_NAMES = ['@theia/plugin', '@wiptheia/plugin'];
const module = require('module');

// add theia object as module into npm cache
NODE_MODULE_NAMES.forEach(moduleName => {
require.cache[moduleName] = {
id: moduleName,
filename: moduleName,
loaded: true,
exports: theia
};
});

// save original resolve method
const internalResolve = module._resolveFilename;

// if we try to resolve theia module, return the filename entry to use cache.
module._resolveFilename = (request: string, parent: {}) => {
if (NODE_MODULE_NAMES.indexOf(request) !== -1) {
return request;
}
const retVal = internalResolve(request, parent);
return retVal;
};
};
43 changes: 43 additions & 0 deletions packages/plugin/src/node/context/backend-init-vscode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/

import { BackendInitializationFn } from '../../common/plugin-protocol';
import { createAPI } from '../../plugin/plugin-context';

export const doInitialization: BackendInitializationFn = (rpc: any) => {
const module = require('module');
const vscodeModuleName = 'vscode';
const theia = createAPI(rpc);

// add theia into global goal as 'vscode'
const g = global as any;
g[vscodeModuleName] = theia;

// add vscode object as module into npm cache
require.cache[vscodeModuleName] = {
id: vscodeModuleName,
filename: vscodeModuleName,
loaded: true,
exports: g[vscodeModuleName]
};

// save original resolve method
const internalResolve = module._resolveFilename;

// if we try to resolve vscode module, return the filename entry to use cache.
module._resolveFilename = (request: string, parent: {}) => {
if (vscodeModuleName === request) {
return request;
}
const retVal = internalResolve(request, parent);
return retVal;
};
};
8 changes: 4 additions & 4 deletions packages/plugin/src/node/hosted-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import * as path from 'path';
import * as cp from "child_process";
import { injectable, inject } from "inversify";
import { Plugin, HostedPluginClient } from '../common/plugin-protocol';
import { HostedPluginClient, PluginModel } from '../common/plugin-protocol';
import { ILogger, ConnectionErrorHandler } from "@theia/core/lib/common";
import { Emitter } from '@theia/core/lib/common/event';
import { createIpcEnv } from "@theia/core/lib/node/messaging/ipc-protocol";
Expand Down Expand Up @@ -37,8 +37,8 @@ export class HostedPluginSupport {
this.client = client;
}

runPlugin(plugin: Plugin): void {
if (plugin.theiaPlugin.node) {
runPlugin(plugin: PluginModel): void {
if (plugin.entryPoint.backend) {
this.runPluginServer(plugin);
}
}
Expand Down Expand Up @@ -75,7 +75,7 @@ export class HostedPluginSupport {
});
}

private runPluginServer(plugin: Plugin): void {
private runPluginServer(plugin: PluginModel): void {
if (this.cp) {
this.terminatePluginServer(this.cp);
}
Expand Down
Loading

0 comments on commit d2c54ff

Please sign in to comment.