diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-resolver.ts b/packages/plugin-ext-vscode/src/node/plugin-vscode-resolver.ts index ada415bd737fa..cf2d6090246cc 100644 --- a/packages/plugin-ext-vscode/src/node/plugin-vscode-resolver.ts +++ b/packages/plugin-ext-vscode/src/node/plugin-vscode-resolver.ts @@ -16,12 +16,20 @@ // tslint:disable:no-any -import { PluginDeployerResolver, PluginDeployerResolverContext } from '@theia/plugin-ext'; -import { injectable } from 'inversify'; +import { PluginDeployerResolver, PluginDeployerResolverContext, PluginDeployerEntry } from '@theia/plugin-ext'; +import { injectable, inject } from 'inversify'; import * as request from 'request'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; +import { PluginDeployerHandler } from '@theia/plugin-ext/lib/common/plugin-protocol'; +import { PluginVsCodeFileHandler } from './plugin-vscode-file-handler'; +import { PluginDeployerFileHandlerContextImpl } from '@theia/plugin-ext/lib/main/node/plugin-deployer-file-handler-context-impl'; +import { PluginDeployerEntryImpl } from '@theia/plugin-ext/lib/main/node/plugin-deployer-entry-impl'; +import { ProxyPluginDeployerEntry } from '@theia/plugin-ext/lib/main/node/plugin-deployer-proxy-entry-impl'; +import { PluginDeployerDirectoryHandlerContextImpl } from '@theia/plugin-ext/lib/main/node/plugin-deployer-directory-handler-context-impl'; +import { PluginVsCodeDirectoryHandler } from './plugin-vscode-directory-handler'; +import { PluginDeployerResolverContextImpl } from '@theia/plugin-ext/lib/main/node/plugin-deployer-resolver-context-impl'; /** * Resolver that handle the vscode: protocol @@ -29,6 +37,9 @@ import * as path from 'path'; @injectable() export class VsCodePluginDeployerResolver implements PluginDeployerResolver { + @inject(PluginDeployerHandler) + protected readonly pluginDeployerHandler: PluginDeployerHandler; + private static PREFIX_VSCODE_EXTENSION = 'vscode:extension/'; private static PREFIX_EXT_INSTALL = 'ext install '; @@ -40,6 +51,9 @@ export class VsCodePluginDeployerResolver implements PluginDeployerResolver { 'Accept': 'application/json;api-version=3.0-preview.1' }; + // All the plugins that are set to install + private toInstall = new Set(); + private vscodeExtensionsFolder: string; constructor() { this.vscodeExtensionsFolder = process.env.VSCODE_PLUGINS || path.resolve(os.tmpdir(), 'vscode-extension-marketplace'); @@ -105,7 +119,11 @@ export class VsCodePluginDeployerResolver implements PluginDeployerResolver { const shortName = extensionName.replace(/\W/g, '_'); const extensionPath = path.resolve(this.vscodeExtensionsFolder, path.basename(shortName + '.vsix')); - const finish = () => { + const finish = async () => { + const pluginEntry = new PluginDeployerEntryImpl(extensionName, originId, extensionPath); + pluginEntry.setResolvedBy((this as any).constructor.name); + this.toInstall.add(extensionName); + await this.downloadVSCodeExtension(pluginResolverContext, pluginEntry); pluginResolverContext.addPlugin(originId, extensionPath); resolve(); }; @@ -125,6 +143,40 @@ export class VsCodePluginDeployerResolver implements PluginDeployerResolver { } + /** + * Download and resolve the extension so that we can inspect the extensionDependencies + */ + private async downloadVSCodeExtension(pluginResolverContext: PluginDeployerResolverContext, plugin: PluginDeployerEntry) { + + const fileHandler = new PluginVsCodeFileHandler(); + const proxyPluginDeployerEntry = new ProxyPluginDeployerEntry(fileHandler, plugin as PluginDeployerEntryImpl); + if (fileHandler.accept(proxyPluginDeployerEntry)) { + const pluginDeployerFileHandlerContext = new PluginDeployerFileHandlerContextImpl(proxyPluginDeployerEntry); + await fileHandler.handle(pluginDeployerFileHandlerContext); + } + + const directoryHandler = new PluginVsCodeDirectoryHandler(); + if (directoryHandler.accept(proxyPluginDeployerEntry)) { + const pluginDeployerDirectoryHandlerContext = new PluginDeployerDirectoryHandlerContextImpl(proxyPluginDeployerEntry); + await directoryHandler.handle(pluginDeployerDirectoryHandlerContext); + } + + const deployDependencies = await this.pluginDeployerHandler.readDeployDependencies(plugin); + + for (const depedency of deployDependencies) { + if (!this.toInstall.has(depedency)) { + const newPluginDeployerContext = new PluginDeployerResolverContextImpl(this, VsCodePluginDeployerResolver.PREFIX_VSCODE_EXTENSION + depedency); + await this.resolve(newPluginDeployerContext); + for (const availablePlugin of newPluginDeployerContext.getPlugins()) { + pluginResolverContext.addPlugin(depedency, availablePlugin.path()); + } + } + } + + this.toInstall.clear(); + return Promise.resolve(); + } + /** * Handle only the plugins that starts with vscode: */ diff --git a/packages/plugin-ext-vscode/src/node/scanner-vscode.ts b/packages/plugin-ext-vscode/src/node/scanner-vscode.ts index 251553d240244..5053e64933212 100644 --- a/packages/plugin-ext-vscode/src/node/scanner-vscode.ts +++ b/packages/plugin-ext-vscode/src/node/scanner-vscode.ts @@ -40,7 +40,8 @@ export class VsCodePluginScanner extends TheiaPluginScanner implements PluginSca }, entryPoint: { backend: plugin.main - } + }, + extensionDependencies: plugin.extensionDependencies }; result.contributes = this.readContributions(plugin); return result; diff --git a/packages/plugin-ext/src/common/plugin-protocol.ts b/packages/plugin-ext/src/common/plugin-protocol.ts index 71da6eb4ddaa7..753aeb66e5217 100644 --- a/packages/plugin-ext/src/common/plugin-protocol.ts +++ b/packages/plugin-ext/src/common/plugin-protocol.ts @@ -49,6 +49,7 @@ export interface PluginPackage { description: string; contributes?: PluginPackageContribution; packagePath: string; + extensionDependencies?: string[]; } export namespace PluginPackage { export function toPluginUrl(pck: PluginPackage, relativePath: string): string { @@ -242,6 +243,8 @@ export interface PluginDeployerResolverContext { addPlugin(pluginId: string, path: string): void; + getPlugins(): PluginDeployerEntry[]; + getOriginId(): string; } @@ -347,6 +350,7 @@ export interface PluginModel { backend?: string; }; contributes?: PluginContribution; + extensionDependencies?: string[]; } /** @@ -575,6 +579,8 @@ export const PluginDeployerHandler = Symbol('PluginDeployerHandler'); export interface PluginDeployerHandler { deployFrontendPlugins(frontendPlugins: PluginDeployerEntry[]): Promise; deployBackendPlugins(backendPlugins: PluginDeployerEntry[]): Promise; + + readDeployDependencies(pluginsToBeInstalled: PluginDeployerEntry): Promise } export const HostedPluginServer = Symbol('HostedPluginServer'); diff --git a/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts b/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts index bad2923a71cdf..c38a9f3dadb3b 100644 --- a/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts +++ b/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts @@ -57,6 +57,16 @@ export class HostedPluginDeployerHandler implements PluginDeployerHandler { return this.currentBackendPluginsMetadata; } + async readDeployDependencies(plugin: PluginDeployerEntry): Promise { + const metadata = await this.reader.getPluginMetadata(plugin.path()); + if (metadata) { + if (metadata.model.extensionDependencies) { + return metadata.model.extensionDependencies; + } + } + return []; + } + async deployFrontendPlugins(frontendPlugins: PluginDeployerEntry[]): Promise { for (const plugin of frontendPlugins) { const metadata = await this.reader.getPluginMetadata(plugin.path()); diff --git a/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts b/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts index 9f34f8d3a4501..40de2afc1dd35 100644 --- a/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts +++ b/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts @@ -94,7 +94,8 @@ export class TheiaPluginScanner implements PluginScanner { entryPoint: { frontend: plugin.theiaPlugin!.frontend, backend: plugin.theiaPlugin!.backend - } + }, + extensionDependencies: plugin.extensionDependencies }; result.contributes = this.readContributions(plugin); return result;