diff --git a/packages/plugin-ext-vscode/src/node/scanner-vscode.ts b/packages/plugin-ext-vscode/src/node/scanner-vscode.ts index cc1b1a96a6624..0a486d348bdd6 100644 --- a/packages/plugin-ext-vscode/src/node/scanner-vscode.ts +++ b/packages/plugin-ext-vscode/src/node/scanner-vscode.ts @@ -21,6 +21,7 @@ import { TheiaPluginScanner } from '@theia/plugin-ext/lib/hosted/node/scanners/s @injectable() export class VsCodePluginScanner extends TheiaPluginScanner implements PluginScanner { private readonly VSCODE_TYPE: PluginEngine = 'vscode'; + private readonly VSCODE_PREFIX: string = 'vscode:extension/'; get apiType(): PluginEngine { return this.VSCODE_TYPE; @@ -41,7 +42,8 @@ export class VsCodePluginScanner extends TheiaPluginScanner implements PluginSca }, entryPoint: { backend: plugin.main - } + }, + extensionDependencies: this.getDeployableDependencies(plugin.extensionDependencies || []) }; result.contributes = this.readContributions(plugin); return result; @@ -55,4 +57,12 @@ export class VsCodePluginScanner extends TheiaPluginScanner implements PluginSca backendInitPath: __dirname + '/plugin-vscode-init.js' }; } + + /** + * Converts an array of extension dependencies + * to an array of deployable extension dependencies + */ + private getDeployableDependencies(dependencies: string[]): string[] { + return dependencies.map(dep => this.VSCODE_PREFIX + dep); + } } diff --git a/packages/plugin-ext/src/common/plugin-protocol.ts b/packages/plugin-ext/src/common/plugin-protocol.ts index 608c5c9d693ef..bd515f9a112c3 100644 --- a/packages/plugin-ext/src/common/plugin-protocol.ts +++ b/packages/plugin-ext/src/common/plugin-protocol.ts @@ -270,6 +270,8 @@ export interface PluginDeployerResolverContext { addPlugin(pluginId: string, path: string): void; + getPlugins(): PluginDeployerEntry[]; + getOriginId(): string; } @@ -375,6 +377,7 @@ export interface PluginModel { backend?: string; }; contributes?: PluginContribution; + extensionDependencies?: string[]; } /** @@ -607,6 +610,8 @@ export const PluginDeployerHandler = Symbol('PluginDeployerHandler'); export interface PluginDeployerHandler { deployFrontendPlugins(frontendPlugins: PluginDeployerEntry[]): Promise; deployBackendPlugins(backendPlugins: PluginDeployerEntry[]): Promise; + + getPluginDependencies(pluginToBeInstalled: 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..633e036e84f37 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 getPluginDependencies(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 975f849d364cd..66764dc656bad 100644 --- a/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts +++ b/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts @@ -72,6 +72,7 @@ const INTERNAL_CONSOLE_OPTIONS_SCHEMA = { @injectable() export class TheiaPluginScanner implements PluginScanner { + private readonly _apiType: PluginEngine = 'theiaPlugin'; @inject(GrammarsReader) @@ -97,7 +98,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; diff --git a/packages/plugin-ext/src/main/node/plugin-deployer-impl.ts b/packages/plugin-ext/src/main/node/plugin-deployer-impl.ts index 4bc4634543edf..3c3d2a13e0f19 100644 --- a/packages/plugin-ext/src/main/node/plugin-deployer-impl.ts +++ b/packages/plugin-ext/src/main/node/plugin-deployer-impl.ts @@ -45,11 +45,6 @@ export class PluginDeployerImpl implements PluginDeployer { @inject(PluginCliContribution) protected readonly cliContribution: PluginCliContribution; - /** - * Deployer entries. - */ - private pluginDeployerEntries: PluginDeployerEntry[]; - /** * Inject all plugin resolvers found at runtime. */ @@ -114,25 +109,55 @@ export class PluginDeployerImpl implements PluginDeployer { } protected async deployMultipleEntries(pluginEntries: string[]): Promise { - // resolve plugins - this.pluginDeployerEntries = await this.resolvePlugins(pluginEntries); - // now that we have plugins check if we have File Handler for them - await this.applyFileHandlers(); + const deployedPlugins = new Set(); + + /** + * Iterate over all the plugins, resolving them one at a time and adding + * in extension dependencies + */ + const futurePlugins = []; + while (pluginEntries.length !== 0) { + const currentPlugin = pluginEntries.pop() as string; + if (deployedPlugins.has(currentPlugin)) { + continue; + } + + // resolve plugins + const pluginDeployerEntries = await this.resolvePlugin(currentPlugin); - // ok now ask for directory handlers - await this.applyDirectoryFileHandlers(); + // now that we have plugins check if we have File Handler for them + await this.applyFileHandlers(pluginDeployerEntries); + + // ok now ask for directory handlers + await this.applyDirectoryFileHandlers(pluginDeployerEntries); + + // add current plugin deployer entries first because dependencies to be installed first + futurePlugins.unshift(...pluginDeployerEntries); + + deployedPlugins.add(currentPlugin); + + // gather all dependencies needed for current plugin + for (const deployerEntry of pluginDeployerEntries) { + const deployDependencies = await this.pluginDeployerHandler.getPluginDependencies(deployerEntry); + pluginEntries.push(...deployDependencies); + } + + } + + await this.deployPlugins(futurePlugins); + + return Promise.resolve(); - await this.deployPlugins(); } /** * deploy all plugins that have been accepted */ - async deployPlugins(): Promise { - const acceptedPlugins = this.pluginDeployerEntries.filter(pluginDeployerEntry => pluginDeployerEntry.isAccepted()); - const acceptedFrontendPlugins = this.pluginDeployerEntries.filter(pluginDeployerEntry => pluginDeployerEntry.isAccepted(PluginDeployerEntryType.FRONTEND)); - const acceptedBackendPlugins = this.pluginDeployerEntries.filter(pluginDeployerEntry => pluginDeployerEntry.isAccepted(PluginDeployerEntryType.BACKEND)); + async deployPlugins(pluginsToDeploy: PluginDeployerEntry[]): Promise { + const acceptedPlugins = pluginsToDeploy.filter(pluginDeployerEntry => pluginDeployerEntry.isAccepted()); + const acceptedFrontendPlugins = pluginsToDeploy.filter(pluginDeployerEntry => pluginDeployerEntry.isAccepted(PluginDeployerEntryType.FRONTEND)); + const acceptedBackendPlugins = pluginsToDeploy.filter(pluginDeployerEntry => pluginDeployerEntry.isAccepted(PluginDeployerEntryType.BACKEND)); this.logger.debug('the accepted plugins are', acceptedPlugins); this.logger.debug('the acceptedFrontendPlugins plugins are', acceptedFrontendPlugins); @@ -157,10 +182,10 @@ export class PluginDeployerImpl implements PluginDeployer { /** * If there are some single files, try to see if we can work on these files (like unpacking it, etc) */ - public async applyFileHandlers(): Promise { + public async applyFileHandlers(pluginDeployerEntries: PluginDeployerEntry[]): Promise { const waitPromises: Array> = []; - this.pluginDeployerEntries.filter(pluginDeployerEntry => pluginDeployerEntry.isResolved()).map(pluginDeployerEntry => { + pluginDeployerEntries.filter(pluginDeployerEntry => pluginDeployerEntry.isResolved()).map(pluginDeployerEntry => { this.pluginDeployerFileHandlers.map(pluginFileHandler => { const proxyPluginDeployerEntry = new ProxyPluginDeployerEntry(pluginFileHandler, (pluginDeployerEntry) as PluginDeployerEntryImpl); if (pluginFileHandler.accept(proxyPluginDeployerEntry)) { @@ -177,10 +202,10 @@ export class PluginDeployerImpl implements PluginDeployer { /** * Check for all registered directories to see if there are some plugins that can be accepted to be deployed. */ - public async applyDirectoryFileHandlers(): Promise { + public async applyDirectoryFileHandlers(pluginDeployerEntries: PluginDeployerEntry[]): Promise { const waitPromises: Array> = []; - this.pluginDeployerEntries.filter(pluginDeployerEntry => pluginDeployerEntry.isResolved()).map(pluginDeployerEntry => { + pluginDeployerEntries.filter(pluginDeployerEntry => pluginDeployerEntry.isResolved()).map(pluginDeployerEntry => { this.pluginDeployerDirectoryHandlers.map(pluginDirectoryHandler => { const proxyPluginDeployerEntry = new ProxyPluginDeployerEntry(pluginDirectoryHandler, (pluginDeployerEntry) as PluginDeployerEntryImpl); if (pluginDirectoryHandler.accept(proxyPluginDeployerEntry)) { @@ -195,32 +220,25 @@ export class PluginDeployerImpl implements PluginDeployer { } /** - * Check a given set of plugin IDs to see if there are some resolvers that can handle them. If there is a matching resolver, then we resolve the plugin + * Check a plugin ID see if there are some resolvers that can handle it. If there is a matching resolver, then we resolve the plugin */ - public async resolvePlugins(pluginIdList: string[]): Promise { + public async resolvePlugin(pluginId: string): Promise { const pluginDeployerEntries: PluginDeployerEntry[] = []; + const foundPluginResolver = this.pluginResolvers.find(pluginResolver => pluginResolver.accept(pluginId)); + // there is a resolver for the input + if (foundPluginResolver) { - // check if accepted ? - const promises = pluginIdList.map(async pluginId => { + // create context object + const context = new PluginDeployerResolverContextImpl(foundPluginResolver, pluginId); - const foundPluginResolver = this.pluginResolvers.find(pluginResolver => pluginResolver.accept(pluginId)); - // there is a resolver for the input - if (foundPluginResolver) { + await foundPluginResolver.resolve(context); - // create context object - const context = new PluginDeployerResolverContextImpl(foundPluginResolver, pluginId); - - await foundPluginResolver.resolve(context); - - context.getPlugins().forEach(entry => pluginDeployerEntries.push(entry)); - } else { - // log it for now - this.logger.error('No plugin resolver found for the entry', pluginId); - pluginDeployerEntries.push(new PluginDeployerEntryImpl(pluginId, pluginId)); - } - // you can do other stuff with the `item` here - }); - await Promise.all(promises); + context.getPlugins().forEach(entry => pluginDeployerEntries.push(entry)); + } else { + // log it for now + this.logger.error('No plugin resolver found for the entry', pluginId); + pluginDeployerEntries.push(new PluginDeployerEntryImpl(pluginId, pluginId)); + } return pluginDeployerEntries; }