提交 aa4948a7 编写于 作者: S Sandeep Somavarapu

#23251 Install post install hook

上级 c348be66
......@@ -100,6 +100,7 @@ function createPaths(environmentService: IEnvironmentService): TPromise<any> {
environmentService.extensionsPath,
environmentService.nodeCachedDataDir,
environmentService.logsPath,
environmentService.globalStorageHome,
environmentService.workspaceStorageHome
];
......
......@@ -103,6 +103,7 @@ export interface IEnvironmentService {
settingsSearchBuildId?: number;
settingsSearchUrl?: string;
globalStorageHome: string;
workspaceStorageHome: string;
backupHome: string;
......
......@@ -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'); }
......
......@@ -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<void> = 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<void> {
const uninstallScript = this.parseUninstallScript(extension);
if (uninstallScript) {
this.logService.info(extension.identifier.id, 'Running Uninstall hook');
postUninstall(extension: ILocalExtension): Promise<void> {
return this.parseAndRun(extension, 'uninstall');
}
postInstall(extension: ILocalExtension): Promise<void> {
return this.parseAndRun(extension, 'install');
}
private async parseAndRun(extension: ILocalExtension, type: string): Promise<void> {
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 = (<string>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 = (<string>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<void> {
return new Promise((c, e) => {
private runLifecycleHook(lifecycleHook: string, args: string[], extension: ILocalExtension): Thenable<void> {
const extensionStoragePath = posix.join(this.environmentService.globalStorageHome, extension.identifier.id.toLocaleLowerCase());
return new Promise<void>((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);
......
......@@ -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.scanExtension(id, location, type))
.then(local =>
this.extensionLifecycle.postInstall(local)
.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;
});
}, error => pfs.rimraf(extensionPath).then(() => Promise.reject(error), () => Promise.reject(error))));
}
private extractAndRename(id: string, zipPath: string, extractPath: string, renamePath: string, token: CancellationToken): Promise<void> {
......@@ -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);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册