Skip to content

Commit

Permalink
Add experiment to implicitly use environment variables for environmen…
Browse files Browse the repository at this point in the history
…t activation (#20651)
  • Loading branch information
Kartik Raj authored Mar 8, 2023
1 parent 7aac96a commit 8a80ebe
Show file tree
Hide file tree
Showing 33 changed files with 688 additions and 154 deletions.
1 change: 0 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ src/client/interpreter/configuration/services/workspaceUpdaterService.ts
src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts
src/client/interpreter/helpers.ts
src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts
src/client/interpreter/activation/service.ts
src/client/interpreter/display/index.ts

src/client/api.ts
Expand Down
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 11 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"publisher": "ms-python",
"enabledApiProposals": [
"quickPickSortByLabel",
"envShellEvent",
"testObserver"
],
"author": {
Expand All @@ -40,7 +41,7 @@
"theme": "dark"
},
"engines": {
"vscode": "^1.75.0-20230123"
"vscode": "^1.76.0"
},
"keywords": [
"python",
Expand Down Expand Up @@ -426,12 +427,14 @@
"enum": [
"All",
"pythonSurveyNotification",
"pythonPromptNewToolsExt"
"pythonPromptNewToolsExt",
"pythonTerminalEnvVarActivation"
],
"enumDescriptions": [
"%python.experiments.All.description%",
"%python.experiments.pythonSurveyNotification.description%",
"%python.experiments.pythonPromptNewToolsExt.description%"
"%python.experiments.pythonPromptNewToolsExt.description%",
"%python.experiments.pythonTerminalEnvVarActivation.description%"
]
},
"scope": "machine",
Expand All @@ -445,12 +448,14 @@
"enum": [
"All",
"pythonSurveyNotification",
"pythonPromptNewToolsExt"
"pythonPromptNewToolsExt",
"pythonTerminalEnvVarActivation"
],
"enumDescriptions": [
"%python.experiments.All.description%",
"%python.experiments.pythonSurveyNotification.description%",
"%python.experiments.pythonPromptNewToolsExt.description%"
"%python.experiments.pythonPromptNewToolsExt.description%",
"%python.experiments.pythonTerminalEnvVarActivation.description%"
]
},
"scope": "machine",
Expand Down Expand Up @@ -1868,7 +1873,7 @@
"@types/stack-trace": "0.0.29",
"@types/tmp": "^0.0.33",
"@types/uuid": "^8.3.4",
"@types/vscode": "^1.74.0",
"@types/vscode": "^1.75.0",
"@types/which": "^2.0.1",
"@types/winreg": "^1.2.30",
"@types/xml2js": "^0.4.2",
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"python.experiments.All.description": "Combined list of all experiments.",
"python.experiments.pythonSurveyNotification.description": "Denotes the Python Survey Notification experiment.",
"python.experiments.pythonPromptNewToolsExt.description": "Denotes the Python Prompt New Tools Extension experiment.",
"python.experiments.pythonTerminalEnvVarActivation.description": "Enables use of environment variables to activate terminals instead of sending activation commands.",
"python.formatting.autopep8Args.description": "Arguments passed in. Each argument is a separate item in the array.",
"python.formatting.autopep8Path.description": "Path to autopep8, you can use a custom version of autopep8 by modifying this setting to include the full path.",
"python.formatting.blackArgs.description": "Arguments passed in. Each argument is a separate item in the array.",
Expand Down
22 changes: 13 additions & 9 deletions src/client/common/application/applicationEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { inject, injectable } from 'inversify';
import * as path from 'path';
import { parse } from 'semver';
import * as vscode from 'vscode';
import { traceError } from '../../logging';
import { Channel } from '../constants';
import { IPlatformService } from '../platform/types';
import { ICurrentProcess, IPathUtils } from '../types';
Expand Down Expand Up @@ -70,19 +71,22 @@ export class ApplicationEnvironment implements IApplicationEnvironment {
public get extensionName(): string {
return this.packageJson.displayName;
}
/**
* At the time of writing this API, the vscode.env.shell isn't officially released in stable version of VS Code.
* Using this in stable version seems to throw errors in VSC with messages being displayed to the user about use of
* unstable API.
* Solution - log and suppress the errors.
* @readonly
* @type {(string)}
* @memberof ApplicationEnvironment
*/

public get shell(): string {
return vscode.env.shell;
}

public get onDidChangeShell(): vscode.Event<string> {
try {
return vscode.env.onDidChangeShell;
} catch (ex) {
traceError('Failed to get onDidChangeShell API', ex);
// `onDidChangeShell` is a proposed API at the time of writing this, so wrap this in a try...catch
// block in case the API is removed or changed.
return new vscode.EventEmitter<string>().event;
}
}

public get packageJson(): any {
return require('../../../../package.json');
}
Expand Down
4 changes: 4 additions & 0 deletions src/client/common/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,10 @@ export interface IApplicationEnvironment {
* @memberof IApplicationShell
*/
readonly shell: string;
/**
* An {@link Event} which fires when the default shell changes.
*/
readonly onDidChangeShell: Event<string>;
/**
* Gets the vscode channel (whether 'insiders' or 'stable').
*/
Expand Down
4 changes: 4 additions & 0 deletions src/client/common/experiments/groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ export enum ShowExtensionSurveyPrompt {
export enum ShowToolsExtensionPrompt {
experiment = 'pythonPromptNewToolsExt',
}

export enum TerminalEnvVarActivation {
experiment = 'pythonTerminalEnvVarActivation',
}
21 changes: 21 additions & 0 deletions src/client/common/experiments/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import { workspace } from 'vscode';
import { isTestExecution } from '../constants';
import { IExperimentService } from '../types';
import { TerminalEnvVarActivation } from './groups';

export function inTerminalEnvVarExperiment(experimentService: IExperimentService): boolean {
if (workspace.workspaceFile && !isTestExecution()) {
// Don't run experiment in multi-root workspaces for now, requires work on VSCode:
// https://github.com/microsoft/vscode/issues/171173
return false;
}
if (!experimentService.inExperimentSync(TerminalEnvVarActivation.experiment)) {
return false;
}
return true;
}
9 changes: 6 additions & 3 deletions src/client/common/process/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { inject, injectable } from 'inversify';
import { traceLog } from '../../logging';
import { IWorkspaceService } from '../application/types';
import { isCI, isTestExecution } from '../constants';
import { Logging } from '../utils/localize';
import { getOSType, getUserHomeDir, OSType } from '../utils/platform';
import { IProcessLogger, SpawnOptions } from './types';
import { escapeRegExp } from 'lodash';
import { replaceAll } from '../stringUtils';
import { identifyShellFromShellPath } from '../terminal/shellDetectors/baseShellDetector';

@injectable()
export class ProcessLogger implements IProcessLogger {
Expand All @@ -27,8 +27,11 @@ export class ProcessLogger implements IProcessLogger {
? [fileOrCommand, ...args].map((e) => e.trimQuotes().toCommandArgumentForPythonExt()).join(' ')
: fileOrCommand;
const info = [`> ${this.getDisplayCommands(command)}`];
if (options && options.cwd) {
info.push(`${Logging.currentWorkingDirectory} ${this.getDisplayCommands(options.cwd)}`);
if (options?.cwd) {
info.push(`cwd: ${this.getDisplayCommands(options.cwd)}`);
}
if (typeof options?.shell === 'string') {
info.push(`shell: ${identifyShellFromShellPath(options?.shell)}`);
}

info.forEach((line) => {
Expand Down
7 changes: 5 additions & 2 deletions src/client/common/terminal/activator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

import { inject, injectable, multiInject } from 'inversify';
import { Terminal } from 'vscode';
import { IConfigurationService } from '../../types';
import { inTerminalEnvVarExperiment } from '../../experiments/helpers';
import { IConfigurationService, IExperimentService } from '../../types';
import { ITerminalActivationHandler, ITerminalActivator, ITerminalHelper, TerminalActivationOptions } from '../types';
import { BaseTerminalActivator } from './base';

Expand All @@ -17,6 +18,7 @@ export class TerminalActivator implements ITerminalActivator {
@inject(ITerminalHelper) readonly helper: ITerminalHelper,
@multiInject(ITerminalActivationHandler) private readonly handlers: ITerminalActivationHandler[],
@inject(IConfigurationService) private readonly configurationService: IConfigurationService,
@inject(IExperimentService) private readonly experimentService: IExperimentService,
) {
this.initialize();
}
Expand All @@ -37,7 +39,8 @@ export class TerminalActivator implements ITerminalActivator {
options?: TerminalActivationOptions,
): Promise<boolean> {
const settings = this.configurationService.getSettings(options?.resource);
const activateEnvironment = settings.terminal.activateEnvironment;
const activateEnvironment =
settings.terminal.activateEnvironment && !inTerminalEnvVarExperiment(this.experimentService);
if (!activateEnvironment || options?.hideFromUser) {
return false;
}
Expand Down
34 changes: 19 additions & 15 deletions src/client/common/terminal/shellDetectors/baseShellDetector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,26 @@ export abstract class BaseShellDetector implements IShellDetector {
terminal?: Terminal,
): TerminalShellType | undefined;
public identifyShellFromShellPath(shellPath: string): TerminalShellType {
// Remove .exe extension so shells can be more consistently detected
// on Windows (including Cygwin).
const basePath = shellPath.replace(/\.exe$/, '');
return identifyShellFromShellPath(shellPath);
}
}

const shell = Array.from(detectableShells.keys()).reduce((matchedShell, shellToDetect) => {
if (matchedShell === TerminalShellType.other) {
const pat = detectableShells.get(shellToDetect);
if (pat && pat.test(basePath)) {
return shellToDetect;
}
export function identifyShellFromShellPath(shellPath: string): TerminalShellType {
// Remove .exe extension so shells can be more consistently detected
// on Windows (including Cygwin).
const basePath = shellPath.replace(/\.exe$/, '');

const shell = Array.from(detectableShells.keys()).reduce((matchedShell, shellToDetect) => {
if (matchedShell === TerminalShellType.other) {
const pat = detectableShells.get(shellToDetect);
if (pat && pat.test(basePath)) {
return shellToDetect;
}
return matchedShell;
}, TerminalShellType.other);
}
return matchedShell;
}, TerminalShellType.other);

traceVerbose(`Shell path '${shellPath}', base path '${basePath}'`);
traceVerbose(`Shell path identified as shell '${shell}'`);
return shell;
}
traceVerbose(`Shell path '${shellPath}', base path '${basePath}'`);
traceVerbose(`Shell path identified as shell '${shell}'`);
return shell;
}
5 changes: 1 addition & 4 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export namespace Interpreters {
export const condaInheritEnvMessage = l10n.t(
'We noticed you\'re using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we recommend that you let the Python extension change "terminal.integrated.inheritEnv" to false in your user settings. [Learn more](https://aka.ms/AA66i8f).',
);
export const activatingTerminals = l10n.t('Reactivating terminals...');
export const activatedCondaEnvLaunch = l10n.t(
'We noticed VS Code was launched from an activated conda environment, would you like to select it?',
);
Expand Down Expand Up @@ -243,10 +244,6 @@ export namespace OutputChannelNames {
export const pythonTest = l10n.t('Python Test Log');
}

export namespace Logging {
export const currentWorkingDirectory = l10n.t('cwd:');
}

export namespace Linters {
export const selectLinter = l10n.t('Select Linter');
}
Expand Down
13 changes: 11 additions & 2 deletions src/client/debugger/extension/configuration/resolvers/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import { getSearchPathEnvVarNames } from '../../../../common/utils/exec';

export const IDebugEnvironmentVariablesService = Symbol('IDebugEnvironmentVariablesService');
export interface IDebugEnvironmentVariablesService {
getEnvironmentVariables(args: LaunchRequestArguments): Promise<EnvironmentVariables>;
getEnvironmentVariables(
args: LaunchRequestArguments,
baseVars?: EnvironmentVariables,
): Promise<EnvironmentVariables>;
}

@injectable()
Expand All @@ -23,7 +26,10 @@ export class DebugEnvironmentVariablesHelper implements IDebugEnvironmentVariabl
@inject(ICurrentProcess) private process: ICurrentProcess,
) {}

public async getEnvironmentVariables(args: LaunchRequestArguments): Promise<EnvironmentVariables> {
public async getEnvironmentVariables(
args: LaunchRequestArguments,
baseVars?: EnvironmentVariables,
): Promise<EnvironmentVariables> {
const pathVariableName = getSearchPathEnvVarNames()[0];

// Merge variables from both .env file and env json variables.
Expand All @@ -37,6 +43,9 @@ export class DebugEnvironmentVariablesHelper implements IDebugEnvironmentVariabl
// "overwrite: true" to ensure that debug-configuration env variable values
// take precedence over env file.
this.envParser.mergeVariables(debugLaunchEnvVars, env, { overwrite: true });
if (baseVars) {
this.envParser.mergeVariables(baseVars, env);
}

// Append the PYTHONPATH and PATH variables.
this.envParser.appendPath(env, debugLaunchEnvVars[pathVariableName]);
Expand Down
13 changes: 12 additions & 1 deletion src/client/debugger/extension/configuration/resolvers/launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { InvalidPythonPathInDebuggerServiceId } from '../../../../application/di
import { IDiagnosticsService, IInvalidPythonPathInDebuggerService } from '../../../../application/diagnostics/types';
import { IConfigurationService } from '../../../../common/types';
import { getOSType, OSType } from '../../../../common/utils/platform';
import { EnvironmentVariables } from '../../../../common/variables/types';
import { IEnvironmentActivationService } from '../../../../interpreter/activation/types';
import { IInterpreterService } from '../../../../interpreter/contracts';
import { DebuggerTypeName } from '../../../constants';
import { DebugOptions, DebugPurpose, LaunchRequestArguments } from '../../../types';
Expand All @@ -24,6 +26,7 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver<Launc
@inject(IConfigurationService) configurationService: IConfigurationService,
@inject(IDebugEnvironmentVariablesService) private readonly debugEnvHelper: IDebugEnvironmentVariablesService,
@inject(IInterpreterService) interpreterService: IInterpreterService,
@inject(IEnvironmentActivationService) private environmentActivationService: IEnvironmentActivationService,
) {
super(configurationService, interpreterService);
}
Expand Down Expand Up @@ -81,6 +84,7 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver<Launc
workspaceFolder: Uri | undefined,
debugConfiguration: LaunchRequestArguments,
): Promise<void> {
const isPythonSet = debugConfiguration.python !== undefined;
if (debugConfiguration.python === undefined) {
debugConfiguration.python = debugConfiguration.pythonPath;
}
Expand All @@ -99,10 +103,17 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver<Launc
const settings = this.configurationService.getSettings(workspaceFolder);
debugConfiguration.envFile = settings.envFile;
}
let baseEnvVars: EnvironmentVariables | undefined;
if (isPythonSet) {
baseEnvVars = await this.environmentActivationService.getActivatedEnvironmentVariables(
workspaceFolder,
await this.interpreterService.getInterpreterDetails(debugConfiguration.python ?? ''),
);
}
// Extract environment variables from .env file in the vscode context and
// set the "env" debug configuration argument. This expansion should be
// done here before handing of the environment settings to the debug adapter
debugConfiguration.env = await this.debugEnvHelper.getEnvironmentVariables(debugConfiguration);
debugConfiguration.env = await this.debugEnvHelper.getEnvironmentVariables(debugConfiguration, baseEnvVars);

if (typeof debugConfiguration.stopOnEntry !== 'boolean') {
debugConfiguration.stopOnEntry = false;
Expand Down
Loading

0 comments on commit 8a80ebe

Please sign in to comment.