提交 62e90329 编写于 作者: S Sandeep Somavarapu

#23251 Post extension install hook

上级 ef95481c
...@@ -26,22 +26,30 @@ export class ExtensionsLifecycle extends Disposable { ...@@ -26,22 +26,30 @@ export class ExtensionsLifecycle extends Disposable {
super(); super();
} }
postUninstall(extension: ILocalExtension): Promise<void> { postUninstall(extension: ILocalExtension): Thenable<void> {
return this.parseAndRun(extension, 'uninstall'); const script = this.parseScript(extension, 'uninstall');
} if (script) {
this.logService.info(extension.identifier.id, `Running post uninstall script`);
postInstall(extension: ILocalExtension): Promise<void> { return this.processesLimiter.queue(() =>
return this.parseAndRun(extension, 'install'); 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<void> { postInstall(extension: ILocalExtension): Thenable<void> {
const script = this.parseScript(extension, type); const script = this.parseScript(extension, 'install');
if (script) { if (script) {
this.logService.info(extension.identifier.id, `Running ${type} hook`); this.logService.info(extension.identifier.id, `Running post install script`);
await this.processesLimiter.queue(() => return this.processesLimiter.queue(() =>
this.runLifecycleHook(script.script, script.args, extension) this.runLifecycleHook(script.script, 'install', script.args, false, 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}`))); .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 { private parseScript(extension: ILocalExtension, type: string): { script: string, args: string[] } | null {
...@@ -57,16 +65,18 @@ export class ExtensionsLifecycle extends Disposable { ...@@ -57,16 +65,18 @@ export class ExtensionsLifecycle extends Disposable {
return null; return null;
} }
private runLifecycleHook(lifecycleHook: string, args: string[], extension: ILocalExtension): Thenable<void> { private runLifecycleHook(lifecycleHook: string, lifecycleType: string, args: string[], timeout: boolean, extension: ILocalExtension): Thenable<void> {
const extensionStoragePath = posix.join(this.environmentService.globalStorageHome, extension.identifier.id.toLocaleLowerCase()); const extensionStoragePath = posix.join(this.environmentService.globalStorageHome, extension.identifier.id.toLocaleLowerCase());
return new Promise<void>((c, e) => { return new Promise<void>((c, e) => {
const extensionLifecycleProcess = this.start(lifecycleHook, args, extension, extensionStoragePath); const extensionLifecycleProcess = this.start(lifecycleHook, lifecycleType, args, extension, extensionStoragePath);
let timeoutHandler; let timeoutHandler;
const onexit = (error?: string) => { const onexit = (error?: string) => {
clearTimeout(timeoutHandler); if (timeoutHandler) {
timeoutHandler = null; clearTimeout(timeoutHandler);
timeoutHandler = null;
}
if (error) { if (error) {
e(error); e(error);
} else { } else {
...@@ -76,28 +86,26 @@ export class ExtensionsLifecycle extends Disposable { ...@@ -76,28 +86,26 @@ export class ExtensionsLifecycle extends Disposable {
// on error // on error
extensionLifecycleProcess.on('error', (err) => { extensionLifecycleProcess.on('error', (err) => {
if (timeoutHandler) { onexit(toErrorMessage(err) || 'Unknown');
onexit(toErrorMessage(err) || 'Unknown');
}
}); });
// on exit // on exit
extensionLifecycleProcess.on('exit', (code: number, signal: string) => { extensionLifecycleProcess.on('exit', (code: number, signal: string) => {
if (timeoutHandler) { onexit(code ? `post-${lifecycleType} process exited with code ${code}` : void 0);
onexit(code ? `Process exited with code ${code}` : void 0);
}
}); });
// timeout: kill process after waiting for 5s if (timeout) {
timeoutHandler = setTimeout(() => { // timeout: kill process after waiting for 5s
timeoutHandler = null; timeoutHandler = setTimeout(() => {
extensionLifecycleProcess.kill(); timeoutHandler = null;
e('timed out'); extensionLifecycleProcess.kill();
}, 5000); 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 = { const opts = {
silent: true, silent: true,
execArgv: undefined, execArgv: undefined,
...@@ -105,19 +113,24 @@ export class ExtensionsLifecycle extends Disposable { ...@@ -105,19 +113,24 @@ export class ExtensionsLifecycle extends Disposable {
VSCODE_EXTENSION_STORAGE_LOCATION: extensionStoragePath 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 // Catch all output coming from the process
type Output = { data: string, format: string[] }; type Output = { data: string, format: string[] };
extensionUninstallProcess.stdout.setEncoding('utf8'); extensionUninstallProcess.stdout.setEncoding('utf8');
extensionUninstallProcess.stderr.setEncoding('utf8'); extensionUninstallProcess.stderr.setEncoding('utf8');
const onStdout = fromNodeEventEmitter<string>(extensionUninstallProcess.stdout, 'data'); const onStdout = fromNodeEventEmitter<string>(extensionUninstallProcess.stdout, 'data');
const onStderr = fromNodeEventEmitter<string>(extensionUninstallProcess.stderr, 'data'); const onStderr = fromNodeEventEmitter<string>(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( const onOutput = anyEvent(
mapEvent(onStdout, o => ({ data: `%c${o}`, format: [''] })), mapEvent(onStdout, o => ({ data: `%c${o}`, format: [''] })),
mapEvent(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] })) mapEvent(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
); );
// Debounce all output, so we can render it in the Chrome console as a group // Debounce all output, so we can render it in the Chrome console as a group
const onDebouncedOutput = debounceEvent<Output>(onOutput, (r, o) => { const onDebouncedOutput = debounceEvent<Output>(onOutput, (r, o) => {
return r return r
...@@ -125,7 +138,7 @@ export class ExtensionsLifecycle extends Disposable { ...@@ -125,7 +138,7 @@ export class ExtensionsLifecycle extends Disposable {
: { data: o.data, format: o.format }; : { data: o.data, format: o.format };
}, 100); }, 100);
// Print out extension host output // Print out output
onDebouncedOutput(data => { onDebouncedOutput(data => {
console.group(extension.identifier.id); console.group(extension.identifier.id);
console.log(data.data, ...data.format); console.log(data.data, ...data.format);
......
...@@ -672,11 +672,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, ...@@ -672,11 +672,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
install(extension: string | IExtension): Promise<void> { install(extension: string | IExtension): Promise<void> {
if (typeof extension === 'string') { if (typeof extension === 'string') {
return this.progressService.withProgress({ return this.installWithProgress(this.extensionService.install(URI.file(extension)).then(extensionIdentifier => this.checkAndEnableDisabledDependencies(extensionIdentifier)));
location: ProgressLocation.Extensions,
title: nls.localize('installingVSIXExtension', 'Installing extension from VSIX...'),
source: `${extension}`
}, () => this.extensionService.install(URI.file(extension)).then(extensionIdentifier => this.checkAndEnableDisabledDependencies(extensionIdentifier)));
} }
if (!(extension instanceof Extension)) { if (!(extension instanceof Extension)) {
...@@ -694,11 +690,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, ...@@ -694,11 +690,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
return Promise.reject(new Error('Missing gallery')); return Promise.reject(new Error('Missing gallery'));
} }
return this.progressService.withProgress({ return this.installWithProgress(
location: ProgressLocation.Extensions, this.extensionService.installFromGallery(gallery)
title: nls.localize('installingMarketPlaceExtension', 'Installing extension from Marketplace....'), .then(() => this.checkAndEnableDisabledDependencies(gallery.identifier))
source: `${extension.id}` , gallery.displayName);
}, () => this.extensionService.installFromGallery(gallery).then(() => this.checkAndEnableDisabledDependencies(gallery.identifier)));
} }
setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void> { setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void> {
...@@ -740,11 +735,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, ...@@ -740,11 +735,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
if (!gallery) { if (!gallery) {
return null; return null;
} }
return this.progressService.withProgress({ return this.installWithProgress(
location: ProgressLocation.Extensions, this.extensionService.installFromGallery(gallery)
source: `${extension.id}` .then(() => this.ignoreAutoUpdate(gallery.identifier.id, version))
}, () => this.extensionService.installFromGallery(gallery)) , gallery.displayName);
.then(() => this.ignoreAutoUpdate(gallery.identifier.id, version));
}); });
} }
...@@ -766,6 +760,14 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, ...@@ -766,6 +760,14 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
}, () => Promise.all(toReinstall.map(local => this.extensionService.reinstallFromGallery(local))).then(() => null)); }, () => Promise.all(toReinstall.map(local => this.extensionService.reinstallFromGallery(local))).then(() => null));
} }
private installWithProgress(installTask: Promise<void>, extensionName?: string): Promise<void> {
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<void> { private checkAndEnableDisabledDependencies(extensionIdentifier: IExtensionIdentifier): Promise<void> {
const extension = this.local.filter(e => (e.local || e.gallery) && areSameExtensions(extensionIdentifier, e.local ? e.local.identifier : e.gallery.identifier))[0]; const extension = this.local.filter(e => (e.local || e.gallery) && areSameExtensions(extensionIdentifier, e.local ? e.local.identifier : e.gallery.identifier))[0];
if (extension) { if (extension) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册