From 653e7e26df3be4cc5185982a8a4780b44523ef8e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 24 Aug 2020 11:09:22 +0200 Subject: [PATCH] sandbox - make lazyEnv fit for sandbox use --- .../parts/sandbox/electron-browser/preload.js | 36 +++++++++++++++ .../parts/sandbox/electron-sandbox/globals.ts | 6 +++ .../electron-browser/workbench/workbench.js | 44 +++++-------------- src/vs/code/electron-main/app.ts | 9 ++-- src/vs/code/node/shellEnv.ts | 19 ++++---- .../node/externalTerminalService.ts | 13 ------ 6 files changed, 69 insertions(+), 58 deletions(-) diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.js b/src/vs/base/parts/sandbox/electron-browser/preload.js index 4dbe1b48a1d..d6bea974701 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.js +++ b/src/vs/base/parts/sandbox/electron-browser/preload.js @@ -93,6 +93,14 @@ process: { platform: process.platform, env: process.env, + _whenEnvResolved: undefined, + get whenEnvResolved() { + if (!this._whenEnvResolved) { + this._whenEnvResolved = resolveEnv(); + } + + return this._whenEnvResolved; + }, on: /** * @param {string} type @@ -157,5 +165,33 @@ return true; } + /** + * If VSCode is not run from a terminal, we should resolve additional + * shell specific environment from the OS shell to ensure we are seeing + * all development related environment variables. We do this from the + * main process because it may involve spawning a shell. + */ + function resolveEnv() { + return new Promise(function (resolve) { + const handle = setTimeout(function () { + console.warn('Preload: Unable to resolve shell environment in a reasonable time'); + + // It took too long to fetch the shell environment, return + resolve(); + }, 3000); + + ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnv) { + clearTimeout(handle); + + // Assign all keys of the shell environment to our process environment + Object.assign(process.env, shellEnv); + + resolve(); + }); + + ipcRenderer.send('vscode:fetchShellEnv'); + }); + } + //#endregion }()); diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 0acd55cb3fe..a305df1c8a3 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -84,6 +84,12 @@ export const process = (window as any).vscode.process as { */ env: { [key: string]: string | undefined }; + /** + * Allows to await resolving the full process environment by checking for the shell environment + * of the OS in certain cases (e.g. when the app is started from the Dock on macOS). + */ + whenEnvResolved: Promise; + /** * A listener on the process. Only a small subset of listener types are allowed. */ diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index d5b7ff6728e..6642ac864eb 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -33,8 +33,8 @@ const bootstrapWindow = (() => { return window.MonacoBootstrapWindow; })(); -// Setup shell environment -process['lazyEnv'] = getLazyEnv(); +// Load environment in parallel to workbench loading to avoid waterfall +const whenEnvResolved = bootstrapWindow.globals().process.whenEnvResolved; // Load workbench main JS, CSS and NLS all in parallel. This is an // optimization to prevent a waterfall of loading to happen, because @@ -45,23 +45,26 @@ bootstrapWindow.load([ 'vs/nls!vs/workbench/workbench.desktop.main', 'vs/css!vs/workbench/workbench.desktop.main' ], - function (workbench, configuration) { + async function (workbench, configuration) { // Mark start of workbench perf.mark('didLoadWorkbenchMain'); performance.mark('workbench-start'); - return process['lazyEnv'].then(function () { - perf.mark('main/startup'); + // Wait for process environment being fully resolved + await whenEnvResolved; - // @ts-ignore - return require('vs/workbench/electron-browser/desktop.main').main(configuration); - }); + perf.mark('main/startup'); + + // @ts-ignore + return require('vs/workbench/electron-browser/desktop.main').main(configuration); }, { removeDeveloperKeybindingsAfterLoad: true, canModifyDOM: function (windowConfig) { - showPartsSplash(windowConfig); + if (!bootstrapWindow.globals().context.sandbox) { + showPartsSplash(windowConfig); // TODO@sandbox non-sandboxed only + } }, beforeLoaderConfig: function (windowConfig, loaderConfig) { loaderConfig.recordStats = true; @@ -171,26 +174,3 @@ function showPartsSplash(configuration) { perf.mark('didShowPartsSplash'); } - -/** - * @returns {Promise} - */ -function getLazyEnv() { - const ipcRenderer = bootstrapWindow.globals().ipcRenderer; - - return new Promise(function (resolve) { - const handle = setTimeout(function () { - resolve(); - console.warn('renderer did not receive lazyEnv in time'); - }, 10000); - - ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnv) { - clearTimeout(handle); - Object.assign(process.env, shellEnv); - // @ts-ignore - resolve(process.env); - }); - - ipcRenderer.send('vscode:fetchShellEnv'); - }); -} diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index e8ff8dda77a..21a4eeb08e0 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -364,17 +364,17 @@ export class CodeApplication extends Disposable { const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); const sharedProcessClient = sharedProcess.whenIpcReady().then(() => { this.logService.trace('Shared process: IPC ready'); + return connect(this.environmentService.sharedIPCHandle, 'main'); }); const sharedProcessReady = sharedProcess.whenReady().then(() => { this.logService.trace('Shared process: init ready'); + return sharedProcessClient; }); this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { this._register(new RunOnceScheduler(async () => { - const userEnv = await getShellEnvironment(this.logService, this.environmentService); - - sharedProcess.spawn(userEnv); + sharedProcess.spawn(await getShellEnvironment(this.logService, this.environmentService)); }, 3000)).schedule(); }); @@ -847,6 +847,9 @@ export class CodeApplication extends Disposable { } catch (error) { this.logService.error(error); } + + // Start to fetch shell environment after window has opened + getShellEnvironment(this.logService, this.environmentService); } private handleRemoteAuthorities(): void { diff --git a/src/vs/code/node/shellEnv.ts b/src/vs/code/node/shellEnv.ts index 174bb673a4a..0383550627a 100644 --- a/src/vs/code/node/shellEnv.ts +++ b/src/vs/code/node/shellEnv.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as cp from 'child_process'; +import { spawn } from 'child_process'; import { generateUuid } from 'vs/base/common/uuid'; import { isWindows } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; @@ -30,7 +30,7 @@ function getUnixShellEnvironment(logService: ILogService): Promise ({})); } - -let _shellEnv: Promise; +let shellEnvPromise: Promise | undefined = undefined; /** * We need to get the environment from a user's shell. @@ -91,21 +90,21 @@ let _shellEnv: Promise; * from within a shell. */ export function getShellEnvironment(logService: ILogService, environmentService: INativeEnvironmentService): Promise { - if (_shellEnv === undefined) { + if (!shellEnvPromise) { if (environmentService.args['disable-user-env-probe']) { logService.trace('getShellEnvironment: disable-user-env-probe set, skipping'); - _shellEnv = Promise.resolve({}); + shellEnvPromise = Promise.resolve({}); } else if (isWindows) { logService.trace('getShellEnvironment: running on Windows, skipping'); - _shellEnv = Promise.resolve({}); + shellEnvPromise = Promise.resolve({}); } else if (process.env['VSCODE_CLI'] === '1' && process.env['VSCODE_FORCE_USER_ENV'] !== '1') { logService.trace('getShellEnvironment: running on CLI, skipping'); - _shellEnv = Promise.resolve({}); + shellEnvPromise = Promise.resolve({}); } else { logService.trace('getShellEnvironment: running on Unix'); - _shellEnv = getUnixShellEnvironment(logService); + shellEnvPromise = getUnixShellEnvironment(logService); } } - return _shellEnv; + return shellEnvPromise; } diff --git a/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts b/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts index 99ac1f65419..42dba3506e5 100644 --- a/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts @@ -18,18 +18,6 @@ import { DEFAULT_TERMINAL_OSX } from 'vs/workbench/contrib/externalTerminal/node const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console"); -type LazyProcess = { - - /** - * The lazy environment is a promise that resolves to `process.env` - * once the process is resolved. The use-case is VS Code running - * on Linux/macOS when being launched via a launcher. Then the env - * (as defined in .bashrc etc) isn't properly set and needs to be - * resolved lazy. - */ - lazyEnv: Promise | undefined; -}; - export class WindowsExternalTerminalService implements IExternalTerminalService { public _serviceBrand: undefined; @@ -318,7 +306,6 @@ export class LinuxExternalTerminalService implements IExternalTerminalService { LinuxExternalTerminalService._DEFAULT_TERMINAL_LINUX_READY = new Promise(async r => { if (env.isLinux) { const isDebian = await pfs.exists('/etc/debian_version'); - await (process as unknown as LazyProcess).lazyEnv; if (isDebian) { r('x-terminal-emulator'); } else if (process.env.DESKTOP_SESSION === 'gnome' || process.env.DESKTOP_SESSION === 'gnome-classic') {