diff --git a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts index 4e69db51e0b82734b53862a59fbbaeec0433bcd6..6a53ee82f87a5799680d0ac0b30e73a1840d2f5f 100644 --- a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts +++ b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts @@ -26,22 +26,30 @@ export class ExtensionsLifecycle extends Disposable { super(); } - postUninstall(extension: ILocalExtension): Promise { - return this.parseAndRun(extension, 'uninstall'); - } - - postInstall(extension: ILocalExtension): Promise { - return this.parseAndRun(extension, 'install'); + postUninstall(extension: ILocalExtension): Thenable { + const script = this.parseScript(extension, 'uninstall'); + if (script) { + this.logService.info(extension.identifier.id, `Running post uninstall script`); + return this.processesLimiter.queue(() => + this.runLifecycleHook(script.script, 'uninstall', script.args, true, extension) + .then(() => this.logService.info(extension.identifier.id, `Finished running post uninstall script`), err => this.logService.error(extension.identifier.id, `Failed to run post uninstall script: ${err}`))); + } + return Promise.resolve(); } - private async parseAndRun(extension: ILocalExtension, type: string): Promise { - const script = this.parseScript(extension, type); + postInstall(extension: ILocalExtension): Thenable { + const script = this.parseScript(extension, 'install'); if (script) { - this.logService.info(extension.identifier.id, `Running ${type} hook`); - await this.processesLimiter.queue(() => - this.runLifecycleHook(script.script, script.args, extension) - .then(() => this.logService.info(extension.identifier.id, `Finished running ${type} hook`), err => this.logService.error(extension.identifier.id, `Failed to run ${type} hook: ${err}`))); + this.logService.info(extension.identifier.id, `Running post install script`); + return this.processesLimiter.queue(() => + this.runLifecycleHook(script.script, 'install', script.args, false, extension) + .then(() => this.logService.info(extension.identifier.id, `Finished running post install script`), + err => { + this.logService.error(extension.identifier.id, `Failed to run post install script: ${err}`); + return Promise.reject(err); + })); } + return Promise.resolve(); } private parseScript(extension: ILocalExtension, type: string): { script: string, args: string[] } | null { @@ -57,16 +65,18 @@ export class ExtensionsLifecycle extends Disposable { return null; } - private runLifecycleHook(lifecycleHook: string, args: string[], extension: ILocalExtension): Thenable { + private runLifecycleHook(lifecycleHook: string, lifecycleType: string, args: string[], timeout: boolean, extension: ILocalExtension): Thenable { const extensionStoragePath = posix.join(this.environmentService.globalStorageHome, extension.identifier.id.toLocaleLowerCase()); return new Promise((c, e) => { - const extensionLifecycleProcess = this.start(lifecycleHook, args, extension, extensionStoragePath); + const extensionLifecycleProcess = this.start(lifecycleHook, lifecycleType, args, extension, extensionStoragePath); let timeoutHandler; const onexit = (error?: string) => { - clearTimeout(timeoutHandler); - timeoutHandler = null; + if (timeoutHandler) { + clearTimeout(timeoutHandler); + timeoutHandler = null; + } if (error) { e(error); } else { @@ -76,28 +86,26 @@ export class ExtensionsLifecycle extends Disposable { // on error extensionLifecycleProcess.on('error', (err) => { - if (timeoutHandler) { - onexit(toErrorMessage(err) || 'Unknown'); - } + onexit(toErrorMessage(err) || 'Unknown'); }); // on exit extensionLifecycleProcess.on('exit', (code: number, signal: string) => { - if (timeoutHandler) { - onexit(code ? `Process exited with code ${code}` : void 0); - } + onexit(code ? `post-${lifecycleType} process exited with code ${code}` : void 0); }); - // timeout: kill process after waiting for 5s - timeoutHandler = setTimeout(() => { - timeoutHandler = null; - extensionLifecycleProcess.kill(); - e('timed out'); - }, 5000); + if (timeout) { + // timeout: kill process after waiting for 5s + timeoutHandler = setTimeout(() => { + timeoutHandler = null; + extensionLifecycleProcess.kill(); + e('timed out'); + }, 5000); + } }); } - private start(uninstallHook: string, args: string[], extension: ILocalExtension, extensionStoragePath: string): ChildProcess { + private start(uninstallHook: string, lifecycleType: string, args: string[], extension: ILocalExtension, extensionStoragePath: string): ChildProcess { const opts = { silent: true, execArgv: undefined, @@ -105,19 +113,24 @@ export class ExtensionsLifecycle extends Disposable { VSCODE_EXTENSION_STORAGE_LOCATION: extensionStoragePath }) }; - const extensionUninstallProcess = fork(uninstallHook, ['--type=extensionUninstall', ...args], opts); + const extensionUninstallProcess = fork(uninstallHook, [`--type=extension-post-${lifecycleType}`, ...args], opts); // Catch all output coming from the process type Output = { data: string, format: string[] }; extensionUninstallProcess.stdout.setEncoding('utf8'); extensionUninstallProcess.stderr.setEncoding('utf8'); + const onStdout = fromNodeEventEmitter(extensionUninstallProcess.stdout, 'data'); const onStderr = fromNodeEventEmitter(extensionUninstallProcess.stderr, 'data'); + + // Log output + onStdout(data => this.logService.info(extension.identifier.id, `post-${lifecycleType}`, data)); + onStderr(data => this.logService.error(extension.identifier.id, `post-${lifecycleType}`, data)); + const onOutput = anyEvent( mapEvent(onStdout, o => ({ data: `%c${o}`, format: [''] })), mapEvent(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] })) ); - // Debounce all output, so we can render it in the Chrome console as a group const onDebouncedOutput = debounceEvent(onOutput, (r, o) => { return r @@ -125,7 +138,7 @@ export class ExtensionsLifecycle extends Disposable { : { data: o.data, format: o.format }; }, 100); - // Print out extension host output + // Print out output onDebouncedOutput(data => { console.group(extension.identifier.id); console.log(data.data, ...data.format); diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 42e13fbc5f2cbe6ff4205c41e2469fcf0ad2675c..873434ebca750ba851adcbb19c52da87d8359f6f 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -672,11 +672,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, install(extension: string | IExtension): Promise { if (typeof extension === 'string') { - return this.progressService.withProgress({ - location: ProgressLocation.Extensions, - title: nls.localize('installingVSIXExtension', 'Installing extension from VSIX...'), - source: `${extension}` - }, () => this.extensionService.install(URI.file(extension)).then(extensionIdentifier => this.checkAndEnableDisabledDependencies(extensionIdentifier))); + return this.installWithProgress(this.extensionService.install(URI.file(extension)).then(extensionIdentifier => this.checkAndEnableDisabledDependencies(extensionIdentifier))); } if (!(extension instanceof Extension)) { @@ -694,11 +690,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, return Promise.reject(new Error('Missing gallery')); } - return this.progressService.withProgress({ - location: ProgressLocation.Extensions, - title: nls.localize('installingMarketPlaceExtension', 'Installing extension from Marketplace....'), - source: `${extension.id}` - }, () => this.extensionService.installFromGallery(gallery).then(() => this.checkAndEnableDisabledDependencies(gallery.identifier))); + return this.installWithProgress( + this.extensionService.installFromGallery(gallery) + .then(() => this.checkAndEnableDisabledDependencies(gallery.identifier)) + , gallery.displayName); } setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise { @@ -740,11 +735,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, if (!gallery) { return null; } - return this.progressService.withProgress({ - location: ProgressLocation.Extensions, - source: `${extension.id}` - }, () => this.extensionService.installFromGallery(gallery)) - .then(() => this.ignoreAutoUpdate(gallery.identifier.id, version)); + return this.installWithProgress( + this.extensionService.installFromGallery(gallery) + .then(() => this.ignoreAutoUpdate(gallery.identifier.id, version)) + , gallery.displayName); }); } @@ -766,6 +760,14 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, }, () => Promise.all(toReinstall.map(local => this.extensionService.reinstallFromGallery(local))).then(() => null)); } + private installWithProgress(installTask: Promise, extensionName?: string): Promise { + const title = extensionName ? nls.localize('installing named extension', "Installing '{0}' extension....", extensionName) : nls.localize('installing extension', 'Installing extension....'); + return this.progressService.withProgress({ + location: ProgressLocation.Extensions, + title + }, () => installTask).then(() => null); + } + private checkAndEnableDisabledDependencies(extensionIdentifier: IExtensionIdentifier): Promise { const extension = this.local.filter(e => (e.local || e.gallery) && areSameExtensions(extensionIdentifier, e.local ? e.local.identifier : e.gallery.identifier))[0]; if (extension) {