提交 52098eae 编写于 作者: B Benjamin Pasero

env - tweak shell resolve experience on startup

上级 fb80c0e4
......@@ -108,18 +108,13 @@
get type() { return 'renderer'; },
get execPath() { return process.execPath; },
_resolveEnv: undefined,
resolveEnv:
/**
* @param userEnv {{[key: string]: string}}
* @returns {Promise<void>}
*/
function (userEnv) {
if (!this._resolveEnv) {
this._resolveEnv = resolveEnv(userEnv);
}
return this._resolveEnv;
return resolveEnv(userEnv);
},
getProcessMemoryInfo:
......@@ -194,6 +189,9 @@
return true;
}
/** @type {Promise<void> | undefined} */
let resolvedEnv = undefined;
/**
* If VSCode is not run from a terminal, we should resolve additional
* shell specific environment from the OS shell to ensure we are seeing
......@@ -204,23 +202,27 @@
* @returns {Promise<void>}
*/
function resolveEnv(userEnv) {
if (!resolvedEnv) {
// Apply `userEnv` directly
Object.assign(process.env, userEnv);
// Apply `userEnv` directly
Object.assign(process.env, userEnv);
// Resolve `shellEnv` from the main side
return new Promise(function (resolve) {
ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnv) {
// Resolve `shellEnv` from the main side
resolvedEnv = new Promise(function (resolve) {
ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnv) {
// Assign all keys of the shell environment to our process environment
// But make sure that the user environment wins in the end
Object.assign(process.env, shellEnv, userEnv);
// Assign all keys of the shell environment to our process environment
// But make sure that the user environment wins in the end
Object.assign(process.env, shellEnv, userEnv);
resolve();
resolve();
});
ipcRenderer.send('vscode:fetchShellEnv');
});
}
ipcRenderer.send('vscode:fetchShellEnv');
});
return resolvedEnv;
}
//#endregion
......
......@@ -35,9 +35,10 @@ export interface ISandboxNodeProcess extends INodeProcess {
readonly execPath: string;
/**
* Resolve the true process environment to use. There are different layers of environment
* that will apply:
* - `process.env`: this is the actual environment of the process
* Resolve the true process environment to use and apply it to `process.env`.
*
* There are different layers of environment that will apply:
* - `process.env`: this is the actual environment of the process before this method
* - `shellEnv` : if the program was not started from a terminal, we resolve all shell
* variables to get the same experience as if the program was started from
* a terminal (Linux, macOS)
......@@ -45,6 +46,9 @@ export interface ISandboxNodeProcess extends INodeProcess {
* from a terminal and changed certain variables
*
* The order of overwrites is `process.env` < `shellEnv` < `userEnv`.
*
* It is critical that every process awaits this method early on startup to get the right
* set of environment in `process.env`.
*/
resolveEnv(userEnv: IProcessEnvironment): Promise<void>;
......
......@@ -83,7 +83,6 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { EncryptionMainService, IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService';
import { ActiveWindowManager } from 'vs/platform/windows/common/windowTracker';
import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from 'vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { DisplayMainService, IDisplayMainService } from 'vs/platform/display/electron-main/displayMainService';
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
......@@ -272,45 +271,45 @@ export class CodeApplication extends Disposable {
let replied = false;
function acceptShellEnv(env: NodeJS.ProcessEnv): void {
clearTimeout(shellEnvTimeoutWarningHandle);
clearTimeout(shellEnvSlowWarningHandle);
clearTimeout(shellEnvTimeoutErrorHandle);
if (!replied) {
webContents.send('vscode:acceptShellEnv', env);
replied = true;
if (!webContents.isDestroyed()) {
webContents.send('vscode:acceptShellEnv', env);
}
}
}
const shellEnvTimeoutWarningHandle = setTimeout(function () {
window?.sendWhenReady('vscode:showShellEnvTimeoutWarning'); // notify inside window if we have one
// Handle slow shell environment resolve calls:
// - a warning after 3s but continue to resolve
// - an error after 10s and stop trying to resolve
const shellEnvSlowWarningHandle = setTimeout(() => window?.sendWhenReady('vscode:showShellEnvSlowWarning'), 3000);
const shellEnvTimeoutErrorHandle = setTimeout(function () {
window?.sendWhenReady('vscode:showShellEnvTimeoutError');
acceptShellEnv({});
}, 10000);
try {
// Prefer to use the args and env from the target window
// when resolving the shell env. It is possible that
// a first window was opened from the UI but a second
// from the CLI and that has implications for wether to
// resolve the shell environment or not.
let args: NativeParsedArgs;
let env: NodeJS.ProcessEnv;
if (window?.config) {
args = window.config;
env = { ...process.env, ...window.config.userEnv };
} else {
args = this.environmentService.args;
env = process.env;
}
// Resolve shell env
const shellEnv = await resolveShellEnv(this.logService, args, env);
acceptShellEnv(shellEnv);
} catch (error) {
window?.sendWhenReady('vscode:showShellEnvError', toErrorMessage(error)); // notify inside window if we have one
acceptShellEnv({});
this.logService.error('Error fetching shell env', error);
// Prefer to use the args and env from the target window
// when resolving the shell env. It is possible that
// a first window was opened from the UI but a second
// from the CLI and that has implications for wether to
// resolve the shell environment or not.
let args: NativeParsedArgs;
let env: NodeJS.ProcessEnv;
if (window?.config) {
args = window.config;
env = { ...process.env, ...window.config.userEnv };
} else {
args = this.environmentService.args;
env = process.env;
}
// Resolve shell env
const shellEnv = await resolveShellEnv(this.logService, args, env);
acceptShellEnv(shellEnv);
});
ipc.on('vscode:toggleDevTools', (event: IpcMainEvent) => event.sender.toggleDevTools());
......
......@@ -685,7 +685,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// (for https://github.com/microsoft/vscode/issues/108571)
const currentUserEnv = (this.currentConfig ?? this.pendingLoadConfig)?.userEnv;
if (currentUserEnv && isLaunchedFromCli(currentUserEnv) && !isLaunchedFromCli(config.userEnv)) {
config.userEnv = currentUserEnv;
config.userEnv = { ...currentUserEnv, ...config.userEnv }; // still allow to override certain environment as passed in
}
// If this is the first time the window is loaded, we associate the paths
......
......@@ -9,6 +9,7 @@ import { isWindows } from 'vs/base/common/platform';
import { ILogService } from 'vs/platform/log/common/log';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
import { toErrorMessage } from 'vs/base/common/errorMessage';
/**
* We need to get the environment from a user's shell.
......@@ -56,7 +57,7 @@ export async function resolveShellEnv(logService: ILogService, args: NativeParse
let unixShellEnvPromise: Promise<typeof process.env> | undefined = undefined;
function doResolveUnixShellEnv(logService: ILogService): Promise<typeof process.env> {
async function doResolveUnixShellEnv(logService: ILogService): Promise<typeof process.env> {
const promise = new Promise<typeof process.env>((resolve, reject) => {
const runAsNode = process.env['ELECTRON_RUN_AS_NODE'];
logService.trace('getUnixShellEnvironment#runAsNode', runAsNode);
......@@ -125,6 +126,11 @@ function doResolveUnixShellEnv(logService: ILogService): Promise<typeof process.
});
});
// swallow errors
return promise.catch(() => ({}));
try {
return await promise;
} catch (error) {
logService.error('getUnixShellEnvironment#error', toErrorMessage(error));
return {}; // ignore any errors
}
}
......@@ -7,7 +7,6 @@ import * as fs from 'fs';
import { basename, normalize, join, posix } from 'vs/base/common/path';
import { localize } from 'vs/nls';
import * as arrays from 'vs/base/common/arrays';
import { mixin } from 'vs/base/common/objects';
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
......@@ -1369,7 +1368,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {
// Build INativeWindowConfiguration from config and options
const configuration: INativeWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI
const configuration = { ...options.cli } as INativeWindowConfiguration;
configuration.appRoot = this.environmentService.appRoot;
configuration.machineId = this.machineId;
configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir;
......
......@@ -246,7 +246,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
diffEditorConfiguration.diffWordWrap = <'off' | 'on' | 'inherit' | undefined>diffEditorConfiguration.wordWrap;
delete diffEditorConfiguration.wordWrap;
objects.mixin(editorConfiguration, diffEditorConfiguration);
Object.assign(editorConfiguration, diffEditorConfiguration);
}
return editorConfiguration;
......
......@@ -67,6 +67,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa
@IEditorGroupsService protected editorGroupService: IEditorGroupsService
) {
super(id, telemetryService, themeService, storageService);
this._instantiationService = instantiationService;
this.editorMemento = this.getEditorMemento<IEditorViewState>(editorGroupService, BaseTextEditor.TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
......
......@@ -32,7 +32,7 @@ import { IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/wo
import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity';
import { isWindows, isMacintosh } from 'vs/base/common/platform';
import { IProductService } from 'vs/platform/product/common/productService';
import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification';
import { INotificationService, IPromptChoice, NeverShowAgainScope, Severity } from 'vs/platform/notification/common/notification';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
......@@ -192,15 +192,19 @@ export class NativeWindow extends Disposable {
run: () => this.openerService.open('https://go.microsoft.com/fwlink/?linkid=2149667')
}];
ipcRenderer.on('vscode:showShellEnvError', (event: unknown, error: string) => this.notificationService.prompt(
Severity.Error,
nls.localize('shellEnvError', "Unable to resolve your shell environment: {0}", error),
choices
ipcRenderer.on('vscode:showShellEnvSlowWarning', () => this.notificationService.prompt(
Severity.Warning,
nls.localize('shellEnvSlowWarning', "Resolving your shell environment is taking very long. Please review your shell configuration."),
choices,
{
sticky: true,
neverShowAgain: { id: 'ignoreShellEnvSlowWarning', scope: NeverShowAgainScope.GLOBAL }
}
));
ipcRenderer.on('vscode:showShellEnvTimeoutWarning', () => this.notificationService.prompt(
Severity.Warning,
nls.localize('shellEnvTimeoutWarning', "Unable to resolve your shell environment in a reasonable time. Please review your shell configuration."),
ipcRenderer.on('vscode:showShellEnvTimeoutError', () => this.notificationService.prompt(
Severity.Error,
nls.localize('shellEnvTimeoutError', "Unable to resolve your shell environment in a reasonable time. Please review your shell configuration."),
choices
));
......
......@@ -193,7 +193,7 @@ export interface IStartupMetrics {
* * Happens in the renderer-process
* * Measured with the `willWaitForShellEnv` and `didWaitForShellEnv` performance marks.
*/
readonly ellapsedWaitForShellEnv?: number;
readonly ellapsedWaitForShellEnv: number;
/**
* The time it took to require the workspace storage DB, connect to it
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册