From aa4948a79d76cee0084d625c28d0433d250ffc96 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 21 Nov 2018 09:00:23 +0100 Subject: [PATCH] #23251 Install post install hook --- src/vs/code/electron-main/main.ts | 1 + .../environment/common/environment.ts | 1 + .../environment/node/environmentService.ts | 3 ++ .../node/extensionLifecycle.ts | 52 ++++++++++++------- .../node/extensionManagementService.ts | 26 +++++----- 5 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 767670d355f..b6c7093f0b6 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -100,6 +100,7 @@ function createPaths(environmentService: IEnvironmentService): TPromise { environmentService.extensionsPath, environmentService.nodeCachedDataDir, environmentService.logsPath, + environmentService.globalStorageHome, environmentService.workspaceStorageHome ]; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 4fb897ac28e..87c7e26a361 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -103,6 +103,7 @@ export interface IEnvironmentService { settingsSearchBuildId?: number; settingsSearchUrl?: string; + globalStorageHome: string; workspaceStorageHome: string; backupHome: string; diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index b1f71fd05c9..1cbd2aad856 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -110,6 +110,9 @@ export class EnvironmentService implements IEnvironmentService { @memoize get appSettingsPath(): string { return path.join(this.appSettingsHome, 'settings.json'); } + @memoize + get globalStorageHome(): string { return path.join(this.appSettingsHome, 'globalStorage'); } + @memoize get workspaceStorageHome(): string { return path.join(this.appSettingsHome, 'workspaceStorage'); } diff --git a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts index 64cd0d545d8..4e69db51e0b 100644 --- a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts +++ b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts @@ -11,44 +11,57 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { posix } from 'path'; import { Limiter } from 'vs/base/common/async'; import { fromNodeEventEmitter, anyEvent, mapEvent, debounceEvent } from 'vs/base/common/event'; +import * as objects from 'vs/base/common/objects'; import { Schemas } from 'vs/base/common/network'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export class ExtensionsLifecycle extends Disposable { private processesLimiter: Limiter = new Limiter(5); // Run max 5 processes in parallel constructor( - @ILogService private logService: ILogService + private environmentService: IEnvironmentService, + private logService: ILogService ) { super(); } - async uninstall(extension: ILocalExtension): Promise { - const uninstallScript = this.parseUninstallScript(extension); - if (uninstallScript) { - this.logService.info(extension.identifier.id, 'Running Uninstall hook'); + postUninstall(extension: ILocalExtension): Promise { + return this.parseAndRun(extension, 'uninstall'); + } + + postInstall(extension: ILocalExtension): Promise { + return this.parseAndRun(extension, 'install'); + } + + private async parseAndRun(extension: ILocalExtension, type: string): Promise { + const script = this.parseScript(extension, type); + if (script) { + this.logService.info(extension.identifier.id, `Running ${type} hook`); await this.processesLimiter.queue(() => - this.runUninstallHook(uninstallScript.uninstallHook, uninstallScript.args, extension) - .then(() => this.logService.info(extension.identifier.id, 'Finished running uninstall hook'), err => this.logService.error(extension.identifier.id, `Failed to run uninstall hook: ${err}`))); + 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}`))); } } - private parseUninstallScript(extension: ILocalExtension): { uninstallHook: string, args: string[] } | null { - if (extension.location.scheme === Schemas.file && extension.manifest && extension.manifest['scripts'] && typeof extension.manifest['scripts']['vscode:uninstall'] === 'string') { - const uninstallScript = (extension.manifest['scripts']['vscode:uninstall']).split(' '); - if (uninstallScript.length < 2 || uninstallScript[0] !== 'node' || !uninstallScript[1]) { - this.logService.warn(extension.identifier.id, 'Uninstall script should be a node script'); + private parseScript(extension: ILocalExtension, type: string): { script: string, args: string[] } | null { + const scriptKey = `vscode:${type}`; + if (extension.location.scheme === Schemas.file && extension.manifest && extension.manifest['scripts'] && typeof extension.manifest['scripts'][scriptKey] === 'string') { + const script = (extension.manifest['scripts'][scriptKey]).split(' '); + if (script.length < 2 || script[0] !== 'node' || !script[1]) { + this.logService.warn(extension.identifier.id, `${scriptKey} should be a node script`); return null; } - return { uninstallHook: posix.join(extension.location.fsPath, uninstallScript[1]), args: uninstallScript.slice(2) || [] }; + return { script: posix.join(extension.location.fsPath, script[1]), args: script.slice(2) || [] }; } return null; } - private runUninstallHook(lifecycleHook: string, args: string[], extension: ILocalExtension): Promise { - return new Promise((c, e) => { + private runLifecycleHook(lifecycleHook: string, args: string[], 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); + const extensionLifecycleProcess = this.start(lifecycleHook, args, extension, extensionStoragePath); let timeoutHandler; const onexit = (error?: string) => { @@ -84,10 +97,13 @@ export class ExtensionsLifecycle extends Disposable { }); } - private start(uninstallHook: string, args: string[], extension: ILocalExtension): ChildProcess { + private start(uninstallHook: string, args: string[], extension: ILocalExtension, extensionStoragePath: string): ChildProcess { const opts = { silent: true, - execArgv: undefined + execArgv: undefined, + env: objects.mixin(objects.deepClone(process.env), { + VSCODE_EXTENSION_STORAGE_LOCATION: extensionStoragePath + }) }; const extensionUninstallProcess = fork(uninstallHook, ['--type=extensionUninstall', ...args], opts); diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 0261a8541f2..5b5120e4be1 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -144,7 +144,7 @@ export class ExtensionManagementService extends Disposable implements IExtension this.uninstalledPath = path.join(this.extensionsPath, '.obsolete'); this.uninstalledFileLimiter = new Queue(); this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this)); - this.extensionLifecycle = this._register(new ExtensionsLifecycle(this.logService)); + this.extensionLifecycle = this._register(new ExtensionsLifecycle(environmentService, this.logService)); this._register(toDisposable(() => { this.installingExtensions.forEach(promise => promise.cancel()); @@ -449,17 +449,17 @@ export class ExtensionManagementService extends Disposable implements IExtension const extensionPath = path.join(location, id); return pfs.rimraf(extensionPath) .then(() => this.extractAndRename(id, zipPath, tempPath, extensionPath, token), e => Promise.reject(new ExtensionManagementError(nls.localize('errorDeleting', "Unable to delete the existing folder '{0}' while installing the extension '{1}'. Please delete the folder manually and try again", extensionPath, id), INSTALL_ERROR_DELETING))) - .then(() => { - this.logService.info('Installation completed.', id); - return this.scanExtension(id, location, type); - }) - .then(local => { - if (metadata) { - local.metadata = metadata; - return this.saveMetadataForLocalExtension(local); - } - return local; - }); + .then(() => this.scanExtension(id, location, type)) + .then(local => + this.extensionLifecycle.postInstall(local) + .then(() => { + this.logService.info('Installation completed.', id); + if (metadata) { + local.metadata = metadata; + return this.saveMetadataForLocalExtension(local); + } + return local; + }, error => pfs.rimraf(extensionPath).then(() => Promise.reject(error), () => Promise.reject(error)))); } private extractAndRename(id: string, zipPath: string, extractPath: string, renamePath: string, token: CancellationToken): Promise { @@ -810,7 +810,7 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(uninstalled => this.scanExtensions(this.extensionsPath, LocalExtensionType.User) // All user extensions .then(extensions => { const toRemove: ILocalExtension[] = extensions.filter(e => uninstalled[e.identifier.id]); - return Promise.all(toRemove.map(e => this.extensionLifecycle.uninstall(e).then(() => this.removeUninstalledExtension(e)))); + return Promise.all(toRemove.map(e => this.extensionLifecycle.postUninstall(e).then(() => this.removeUninstalledExtension(e)))); }) ).then(() => null); } -- GitLab