From 3294ae66b8997b2aec11a0c9180fb9c2ceb017fa Mon Sep 17 00:00:00 2001 From: Igor Vinokur Date: Fri, 28 Dec 2018 17:51:27 +0200 Subject: [PATCH] CHE-11954: Add che.registerTaskRunner Theia Plugin API --- .gitignore | 2 + .../eclipse-che-theia-plugin-ext/package.json | 1 + .../src/browser/che-api-provider.ts | 2 + .../src/browser/che-frontend-module.ts | 16 +++- .../src/browser/che-task-client.ts | 51 ++++++++++ .../src/browser/che-task-main.ts | 35 +++++++ .../src/common/che-protocol.ts | 35 +++++++ .../src/node/che-backend-module.ts | 22 ++++- .../src/node/che-task-service.ts | 95 +++++++++++++++++++ .../src/plugin/api-factory.ts | 11 ++- .../src/plugin/che-task-impl.ts | 59 ++++++++++++ .../src/che-proposed.d.ts | 39 ++++++++ yarn.lock | 17 +++- 13 files changed, 380 insertions(+), 5 deletions(-) create mode 100644 extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-client.ts create mode 100644 extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-main.ts create mode 100644 extensions/eclipse-che-theia-plugin-ext/src/node/che-task-service.ts create mode 100644 extensions/eclipse-che-theia-plugin-ext/src/plugin/che-task-impl.ts diff --git a/.gitignore b/.gitignore index 820bf85c1e..3a08ffb506 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules yarn-error.log .DS_Store +.idea + diff --git a/extensions/eclipse-che-theia-plugin-ext/package.json b/extensions/eclipse-che-theia-plugin-ext/package.json index 6375ad81b4..da0cf6dbb3 100644 --- a/extensions/eclipse-che-theia-plugin-ext/package.json +++ b/extensions/eclipse-che-theia-plugin-ext/package.json @@ -15,6 +15,7 @@ "dependencies": { "@theia/core": "0.3.17", "@theia/plugin-ext": "0.3.17", + "@theia/task": "0.3.17", "@eclipse-che/plugin": "^0.0.1-1544617370", "@eclipse-che/workspace-client": "^0.0.1-1545387823" }, diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-api-provider.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-api-provider.ts index 1e50554079..6d8aa82317 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-api-provider.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-api-provider.ts @@ -13,6 +13,7 @@ import { injectable, interfaces } from 'inversify'; import { PLUGIN_RPC_CONTEXT } from '../common/che-protocol'; import { CheApiMainImpl } from './che-api-main-impl'; import { CheVariablesMainImpl } from './che-variables-main'; +import { CheTaskMainImpl } from './che-task-main'; @injectable() export class CheApiProvider implements MainPluginApiProvider { @@ -20,6 +21,7 @@ export class CheApiProvider implements MainPluginApiProvider { initialize(rpc: RPCProtocol, container: interfaces.Container): void { rpc.set(PLUGIN_RPC_CONTEXT.CHE_API_MAIN, new CheApiMainImpl(container)); rpc.set(PLUGIN_RPC_CONTEXT.CHE_VARIABLES_MAIN, new CheVariablesMainImpl(container, rpc)); + rpc.set(PLUGIN_RPC_CONTEXT.CHE_TASK_MAIN, new CheTaskMainImpl(container, rpc)); } } diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts index 4f89493d9f..4d16b05776 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-frontend-module.ts @@ -11,8 +11,15 @@ import { ContainerModule } from 'inversify'; import { MainPluginApiProvider } from '@theia/plugin-ext/lib/common/plugin-ext-api-contribution'; import { CheApiProvider } from './che-api-provider'; -import { CheApiService, CHE_API_SERVICE_PATH } from '../common/che-protocol'; +import { + CHE_API_SERVICE_PATH, + CHE_TASK_SERVICE_PATH, + CheApiService, + CheTaskClient, + CheTaskService +} from '../common/che-protocol'; import { WebSocketConnectionProvider } from '@theia/core/lib/browser'; +import { CheTaskClientImpl } from './che-task-client'; export default new ContainerModule(bind => { bind(CheApiProvider).toSelf().inSingletonScope(); @@ -22,4 +29,11 @@ export default new ContainerModule(bind => { const provider = ctx.container.get(WebSocketConnectionProvider); return provider.createProxy(CHE_API_SERVICE_PATH); }).inSingletonScope(); + + bind(CheTaskClient).to(CheTaskClientImpl).inSingletonScope(); + bind(CheTaskService).toDynamicValue(ctx => { + const provider = ctx.container.get(WebSocketConnectionProvider); + const client: CheTaskClient = ctx.container.get(CheTaskClient); + return provider.createProxy(CHE_TASK_SERVICE_PATH, client); + }).inSingletonScope(); }); diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-client.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-client.ts new file mode 100644 index 0000000000..5294c9e055 --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-client.ts @@ -0,0 +1,51 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import { CheTaskClient } from '../common/che-protocol'; +import { Emitter, Event } from '@theia/core'; +import { injectable } from 'inversify'; +import { TaskConfiguration, TaskInfo } from '@eclipse-che/plugin'; + +@injectable() +export class CheTaskClientImpl implements CheTaskClient { + private readonly onKillEventEmitter: Emitter; + private taskInfoHandler: ((id: number) => Promise) | undefined; + private runTaskHandler: ((config: TaskConfiguration, ctx?: string) => Promise) | undefined; + constructor() { + this.onKillEventEmitter = new Emitter(); + } + + async runTask(taskConfig: TaskConfiguration, ctx?: string): Promise { + if (this.runTaskHandler) { + return await this.runTaskHandler(taskConfig, ctx); + } + } + + async getTaskInfo(id: number): Promise { + if (this.taskInfoHandler) { + return await this.taskInfoHandler(id); + } + } + + get onKillEvent(): Event { + return this.onKillEventEmitter.event; + } + + async killTask(id: number): Promise { + this.onKillEventEmitter.fire(id); + } + + setTaskInfoHandler(handler: (id: number) => Promise) { + this.taskInfoHandler = handler; + } + + setRunTaskHandler(handler: (config: TaskConfiguration, ctx?: string) => Promise) { + this.runTaskHandler = handler; + } +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-main.ts b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-main.ts new file mode 100644 index 0000000000..b1215c2c27 --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-ext/src/browser/che-task-main.ts @@ -0,0 +1,35 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import { CheTask, CheTaskMain, CheTaskService, CheTaskClient, PLUGIN_RPC_CONTEXT } from '../common/che-protocol'; +import { RPCProtocol } from '@theia/plugin-ext/lib/api/rpc-protocol'; +import { interfaces, injectable } from 'inversify'; + +@injectable() +export class CheTaskMainImpl implements CheTaskMain{ + private readonly delegate: CheTaskService; + private readonly cheTaskClient: CheTaskClient; + constructor(container: interfaces.Container, rpc: RPCProtocol) { + const proxy: CheTask = rpc.getProxy(PLUGIN_RPC_CONTEXT.CHE_TASK); + this.delegate = container.get(CheTaskService); + this.cheTaskClient = container.get(CheTaskClient); + this.cheTaskClient.onKillEvent(id => { + proxy.$killTask(id); + }); + this.cheTaskClient.setTaskInfoHandler(id => proxy.$getTaskInfo(id)); + this.cheTaskClient.setRunTaskHandler((config, ctx) => proxy.$runTask(config, ctx)); + } + async $registerTaskRunner(type: string): Promise { + return await this.delegate.registerTaskRunner(type); + } + + async $disposeTaskRunner(type: string): Promise { + await this.delegate.disposeTaskRunner(type); + } +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts b/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts index e78b6b70d5..006acab665 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/common/che-protocol.ts @@ -10,6 +10,7 @@ import { ProxyIdentifier, createProxyIdentifier } from '@theia/plugin-ext/lib/api/rpc-protocol'; import * as che from '@eclipse-che/plugin'; +import { Event, JsonRpcServer } from '@theia/core'; export interface CheApiPlugin { } @@ -31,6 +32,19 @@ export interface CheVariablesMain { $resolve(value: string): Promise; } +export interface CheTask { + registerTaskRunner(type: string, runner: che.TaskRunner): Promise; + $runTask(config: che.TaskConfiguration, ctx?: string): Promise; + $killTask(id: number): Promise; + $getTaskInfo(id: number): Promise; +} + +export const CheTaskMain = Symbol('CheTaskMain'); +export interface CheTaskMain { + $registerTaskRunner(type: string): Promise; + $disposeTaskRunner(type: string): Promise; +} + export interface Variable { name: string, description: string, @@ -285,6 +299,8 @@ export const PLUGIN_RPC_CONTEXT = { CHE_API_MAIN: >createProxyIdentifier('CheApiMain'), CHE_VARIABLES: >createProxyIdentifier('CheVariables'), CHE_VARIABLES_MAIN: >createProxyIdentifier('CheVariablesMain'), + CHE_TASK: >createProxyIdentifier('CheTask'), + CHE_TASK_MAIN: >createProxyIdentifier('CheTaskMain'), }; // Theia RPC protocol @@ -300,3 +316,22 @@ export interface CheApiService { getFactoryById(factoryId: string): Promise; } + +export const CHE_TASK_SERVICE_PATH = '/che-task-service'; + +export const CheTaskService = Symbol('CheTaskService'); +export interface CheTaskService extends JsonRpcServer{ + registerTaskRunner(type: string): Promise; + disposeTaskRunner(type: string): Promise; + disconnectClient(client: CheTaskClient): void; +} + +export const CheTaskClient = Symbol('CheTaskClient'); +export interface CheTaskClient { + runTask(taskConfig: che.TaskConfiguration, ctx?: string): Promise; + killTask(id: number): Promise; + getTaskInfo(id: number): Promise; + setTaskInfoHandler(func: (id: number) => Promise): void; + setRunTaskHandler(func: (config: che.TaskConfiguration, ctx?: string) => Promise): void; + onKillEvent: Event +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/node/che-backend-module.ts b/extensions/eclipse-che-theia-plugin-ext/src/node/che-backend-module.ts index 12fe3ad32f..e5cd9e90bd 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/node/che-backend-module.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/node/che-backend-module.ts @@ -15,7 +15,14 @@ import { ChePluginApiContribution } from './che-plugin-script-service'; import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application'; import { ConnectionHandler, JsonRpcConnectionHandler } from '@theia/core'; import { CheApiServiceImpl } from './che-api-service'; -import { CheApiService, CHE_API_SERVICE_PATH } from '../common/che-protocol'; +import { + CHE_API_SERVICE_PATH, + CHE_TASK_SERVICE_PATH, + CheApiService, + CheTaskClient, + CheTaskService +} from '../common/che-protocol'; +import {CheTaskServiceImpl} from "./che-task-service"; export default new ContainerModule(bind => { bind(ChePluginApiProvider).toSelf().inSingletonScope(); @@ -30,4 +37,17 @@ export default new ContainerModule(bind => { ctx.container.get(CheApiService) ) ).inSingletonScope(); + + bind(CheTaskService).toDynamicValue(ctx => new CheTaskServiceImpl(ctx.container)).inSingletonScope(); + bind(ConnectionHandler).toDynamicValue(ctx => + new JsonRpcConnectionHandler(CHE_TASK_SERVICE_PATH, client => { + const server: CheTaskService = ctx.container.get(CheTaskService); + server.setClient(client); + client.onDidCloseConnection(() => { + server.disconnectClient(client); + }); + return server; + } + ) + ).inSingletonScope(); }); diff --git a/extensions/eclipse-che-theia-plugin-ext/src/node/che-task-service.ts b/extensions/eclipse-che-theia-plugin-ext/src/node/che-task-service.ts new file mode 100644 index 0000000000..127db3683f --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-ext/src/node/che-task-service.ts @@ -0,0 +1,95 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import { CheTaskClient, CheTaskService } from '../common/che-protocol'; +import { injectable, interfaces } from 'inversify'; +import { Task, TaskManager, TaskOptions, TaskRunnerRegistry } from '@theia/task/lib/node' +import { Disposable, ILogger } from '@theia/core'; +import { TaskConfiguration, TaskInfo } from '@theia/task/lib/common/task-protocol'; + +@injectable() +export class CheTaskServiceImpl implements CheTaskService { + private readonly runnerRegistry: TaskRunnerRegistry; + private readonly taskManager: TaskManager; + private readonly logger: ILogger; + private readonly disposableMap: Map; + private readonly clients: CheTaskClient[]; + constructor(container: interfaces.Container) { + this.runnerRegistry = container.get(TaskRunnerRegistry); + this.taskManager = container.get(TaskManager); + this.logger = container.get(ILogger); + this.disposableMap = new Map(); + this.clients = []; + } + + async registerTaskRunner(type: string): Promise { + const runner = { + async run(taskConfig: TaskConfiguration, ctx?: string): Promise { + return runTask(taskConfig, ctx); + } + }; + this.disposableMap.set(type, this.runnerRegistry.registerRunner(type, runner)); + const runTask = (config: TaskConfiguration, ctx?: string): Task => { + this.clients.forEach(client => client.runTask(config, ctx)); + return new CheTask(this.taskManager, this.logger, { label: config.label, config }, this.clients); + }; + } + + dispose() { + // do nothing + } + + setClient(client: CheTaskClient ){ + this.clients.push(client); + } + + async disposeTaskRunner(type: string): Promise { + const disposable = this.disposableMap.get(type); + if (disposable) { + disposable.dispose(); + } + } + + async disconnectClient(client: CheTaskClient) { + const idx = this.clients.indexOf(client); + if (idx > -1) { + this.clients.splice(idx, 1); + } + } +} + +class CheTask extends Task { + private readonly clients: CheTaskClient[]; + constructor(taskManager: TaskManager, + logger: ILogger, + options: TaskOptions, + clients: CheTaskClient[]) { + super(taskManager, logger, options); + this.clients = clients; + } + + getRuntimeInfo(): TaskInfo { + // for (const client of this.clients) { + // const taskInfo = await client.getTaskInfo(this.taskId); + // if (taskInfo) { + // return { + // taskId: this.taskId, + // terminalId: taskInfo.terminalId, + // ctx: taskInfo.ctx, + // config: taskInfo.config + // }; + // } + // } + throw new Error('Information not found'); + } + + async kill(): Promise { + this.clients.forEach(client => client.killTask(this.taskId)); + } +} diff --git a/extensions/eclipse-che-theia-plugin-ext/src/plugin/api-factory.ts b/extensions/eclipse-che-theia-plugin-ext/src/plugin/api-factory.ts index 12b2501ea7..aab51a5339 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/plugin/api-factory.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/plugin/api-factory.ts @@ -21,6 +21,7 @@ import { import { CheApiPluginImpl } from './che-workspace'; import { CheVariablesImpl } from './che-variables-impl'; import { PLUGIN_RPC_CONTEXT } from '../common/che-protocol'; +import { CheTaskImpl } from './che-task-impl'; export interface ApiFactory { (plugin: Plugin): typeof che; } @@ -28,6 +29,7 @@ export interface ApiFactory { export function createAPIFactory(rpc: RPCProtocol): ApiFactory { const chePluginImpl = new CheApiPluginImpl(rpc); const cheVariablesImpl = rpc.set(PLUGIN_RPC_CONTEXT.CHE_VARIABLES, new CheVariablesImpl(rpc)); + const cheTaskImpl = rpc.set(PLUGIN_RPC_CONTEXT.CHE_TASK, new CheTaskImpl(rpc)); return function (plugin: Plugin): typeof che { const ws: typeof che.workspace = { @@ -82,10 +84,17 @@ export function createAPIFactory(rpc: RPCProtocol): ApiFactory { } }; + const task: typeof che.task = { + registerTaskRunner(type: string, runner: che.TaskRunner): Promise { + return cheTaskImpl.registerTaskRunner(type, runner); + } + }; + return { workspace: ws, factory, - variables: variable + variables: variable, + task }; }; } diff --git a/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-task-impl.ts b/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-task-impl.ts new file mode 100644 index 0000000000..3c3ab50e83 --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-ext/src/plugin/che-task-impl.ts @@ -0,0 +1,59 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import { CheTask, CheTaskMain, PLUGIN_RPC_CONTEXT } from '../common/che-protocol'; +import { TaskRunner, Disposable, Task, TaskInfo } from '@eclipse-che/plugin'; +import { RPCProtocol } from '@theia/plugin-ext/lib/api/rpc-protocol'; +import { TaskConfiguration } from '@theia/task/lib/common'; + +export class CheTaskImpl implements CheTask { + private readonly cheTaskMain: CheTaskMain; + private readonly runnerMap: Map; + private readonly taskArray: Task[]; + constructor(rpc: RPCProtocol) { + this.cheTaskMain = rpc.getProxy(PLUGIN_RPC_CONTEXT.CHE_TASK_MAIN); + this.runnerMap = new Map(); + this.taskArray = []; + } + async registerTaskRunner(type: string, runner: TaskRunner): Promise { + this.runnerMap.set(type, runner); + await this.cheTaskMain.$registerTaskRunner(type); + return { + dispose: async () => { + await this.cheTaskMain.$disposeTaskRunner(type); + } + } + } + + async $runTask(config: TaskConfiguration, ctx?: string): Promise { + const runner = this.runnerMap.get(config.type); + if (runner) { + const task = await runner.run(config, ctx); + this.taskArray.push(task); + } + } + + async $killTask(id: number): Promise { + const task = this.taskArray.find(task => task.getRuntimeInfo().taskId === id); + if (task) { + await task.kill(); + const idx = this.taskArray.indexOf(task); + if (idx > -1) { + this.taskArray.splice(idx, 1); + } + } + } + + async $getTaskInfo(id: number): Promise { + const task = this.taskArray.find(task => task.getRuntimeInfo().taskId === id); + if (task) { + return task.getRuntimeInfo(); + } + } +} diff --git a/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts b/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts index 1167d1198a..188d69b631 100644 --- a/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts +++ b/extensions/eclipse-che-theia-plugin/src/che-proposed.d.ts @@ -335,4 +335,43 @@ declare module '@eclipse-che/plugin' { dispose(): PromiseLike; } + export namespace task { + export function registerTaskRunner(type: string, runner: TaskRunner): Promise; + } + + /** A Task Runner knows how to setConfig and kill a Task of a particular type. */ + export interface TaskRunner { + /** Runs a task based on the given task configuration. */ + run(taskConfig: TaskConfiguration, ctx?: string): Promise; + } + + export interface Task { + /** Terminates the task. */ + kill(): Promise; + /** Returns runtime information about task. */ + getRuntimeInfo(): TaskInfo; + } + + /** Runtime information about Task. */ + export interface TaskInfo { + /** internal unique task id */ + readonly taskId: number, + /** terminal id. Defined if task is run as a terminal process */ + readonly terminalId?: number, + /** context that was passed as part of task creation, if any */ + readonly ctx?: string, + /** task config used for launching a task */ + readonly config: TaskConfiguration, + /** Additional properties specific for a particular Task Runner. */ + readonly [key: string]: string; + } + + export interface TaskConfiguration { + readonly type: string; + /** A label that uniquely identifies a task configuration */ + readonly label: string; + + /** Additional task type specific properties. */ + readonly [key: string]: string; + } } diff --git a/yarn.lock b/yarn.lock index f7c7dded45..cdbc20b5f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -955,6 +955,19 @@ node-pty "0.7.6" string-argv "^0.1.1" +"@theia/task@0.3.17": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@theia/task/-/task-0.3.17.tgz#554491af37ceda407602e82f70f2c98b3e66c769" + integrity sha512-IQkGH/gF+wWbN/xdKMolZyDqz19joyrPByKd8MWSgSWiQNAQKOLhjHjWmA3j2GAQioZkwJy8VtfykCOMc7NB+w== + dependencies: + "@theia/core" "^0.3.17" + "@theia/markers" "^0.3.17" + "@theia/process" "^0.3.17" + "@theia/terminal" "^0.3.17" + "@theia/variable-resolver" "^0.3.17" + "@theia/workspace" "^0.3.17" + jsonc-parser "^2.0.2" + "@theia/terminal@^0.3.17": version "0.3.17" resolved "https://registry.yarnpkg.com/@theia/terminal/-/terminal-0.3.17.tgz#7475a8a090619449ab665270e5ab5050e8058cb3" @@ -4908,9 +4921,9 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" -"moxios@git://github.com/stoplightio/moxios#v1.3.0": +"moxios@git://github.com/stoplightio/moxios.git#v1.3.0": version "1.3.0" - resolved "git://github.com/stoplightio/moxios#9d702c8eafee4b02917d6bc400ae15f1e835cf51" + resolved "git://github.com/stoplightio/moxios.git#9d702c8eafee4b02917d6bc400ae15f1e835cf51" dependencies: class-autobind "^0.1.4"