Skip to content

Commit e1964fd

Browse files
authored
Run python code using env variables from activated envs (#3930)
* Misc * Run python code using env variables from activated envs * Improve readability * Give preference to version returned by python process * Fixed errors, ensure vsc, console does not leak into debug adapter code * More tests for caching * Update * Use default shells * Change time * Log command * Rename PipEnv to Pipenv * Add telemetry * Log errors * Add news entry
1 parent 27f485b commit e1964fd

File tree

72 files changed

+1780
-520
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+1780
-520
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Fixes
66

7+
78
1. Lowering threshold for Language Server support on a platform.
89
([#3693](https://github.com/Microsoft/vscode-python/issues/3693))
910
1. Fix bug affecting multiple linters used in a workspace.

gulpfile.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,6 @@ gulp.task("compile", () => {
154154

155155

156156
gulp.task('compile-webviews', async () => spawnAsync('npx', ['webpack', '--config', 'webpack.datascience-ui.config.js', '--mode', 'production']));
157-
gulp.task('webpack', async () => {
158-
await spawnAsync('npx', ['webpack', '--mode', 'production', '--inline', '--progress']);
159-
await spawnAsync('npx', ['webpack', '--config', './build/webpack/webpack.extension.config.js', '--mode', 'production', '--inline', '--progress']);
160-
});
161157

162158
gulp.task('webpack', async () => {
163159
await spawnAsync('npx', ['webpack', '--mode', 'production']);
@@ -708,6 +704,7 @@ function getFilesToProcess(fileList) {
708704
* @param {hygieneOptions} options
709705
*/
710706
function getFileListToProcess(options) {
707+
return [];
711708
const mode = options ? options.mode : 'all';
712709
const gulpSrcOptions = { base: '.' };
713710

news/1 Enhancements/2855.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Activate `pipenv` environments in the shell using the command `pipenv shell`.

news/2 Fixes/3330.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Activate any selected Python Environment when running unit tests.

news/2 Fixes/3953.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Remove duplicates from interpreters listed in the interpreter selection list.

news/3 Code Health/3746.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Detect usage of `xonsh` shells (this does **not** add support for `xonsh` itself)

pythonFiles/printEnvVariables.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import os
5+
import json
6+
7+
print(json.dumps(dict(os.environ)))

src/client/common/logger.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class Logger implements ILogger {
4646
}
4747
}
4848

49-
enum LogOptions {
49+
export enum LogOptions {
5050
None = 0,
5151
Arguments = 1,
5252
ReturnValue = 2
@@ -57,6 +57,9 @@ function argsToLogString(args: any[]): string {
5757
try {
5858
return (args || []).map((item, index) => {
5959
try {
60+
if (item.fsPath) {
61+
return `Arg ${index + 1}: <Uri:${item.fsPath}>`;
62+
}
6063
return `Arg ${index + 1}: ${JSON.stringify(item)}`;
6164
} catch {
6265
return `Arg ${index + 1}: UNABLE TO DETERMINE VALUE`;
@@ -69,15 +72,18 @@ function argsToLogString(args: any[]): string {
6972

7073
// tslint:disable-next-line:no-any
7174
function returnValueToLogString(returnValue: any): string {
72-
let returnValueMessage = 'Return Value: ';
73-
if (returnValue) {
74-
try {
75-
returnValueMessage += `${JSON.stringify(returnValue)}`;
76-
} catch {
77-
returnValueMessage += 'UNABLE TO DETERMINE VALUE';
78-
}
75+
const returnValueMessage = 'Return Value: ';
76+
if (returnValue === undefined) {
77+
return `${returnValueMessage}undefined`;
78+
}
79+
if (returnValue === null) {
80+
return `${returnValueMessage}null`;
81+
}
82+
try {
83+
return `${returnValueMessage}${JSON.stringify(returnValue)}`;
84+
} catch {
85+
return `${returnValueMessage}<Return value cannot be serialized for logging>`;
7986
}
80-
return returnValueMessage;
8187
}
8288

8389
export function traceVerbose(message: string) {
@@ -91,10 +97,10 @@ export function traceInfo(message: string) {
9197
}
9298

9399
export namespace traceDecorators {
94-
export function verbose(message: string) {
95-
return trace(message, LogOptions.Arguments | LogOptions.ReturnValue);
100+
export function verbose(message: string, options: LogOptions = LogOptions.Arguments | LogOptions.ReturnValue) {
101+
return trace(message, options);
96102
}
97-
export function error(message: string, ex?: Error) {
103+
export function error(message: string) {
98104
return trace(message, LogOptions.Arguments | LogOptions.ReturnValue, LogLevel.Error);
99105
}
100106
export function info(message: string) {

src/client/common/platform/fileSystem.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import * as glob from 'glob';
99
import { inject, injectable } from 'inversify';
1010
import * as path from 'path';
1111
import * as tmp from 'tmp';
12-
import { traceDecorators } from '../logger';
1312
import { createDeferred } from '../utils/async';
1413
import { IFileSystem, IPlatformService, TemporaryFile } from './types';
1514

@@ -144,7 +143,6 @@ export class FileSystem implements IFileSystem {
144143
return deferred.promise;
145144
}
146145

147-
@traceDecorators.error('Failed to get FileHash')
148146
public getFileHash(filePath: string): Promise<string | undefined> {
149147
return new Promise<string | undefined>(resolve => {
150148
fs.lstat(filePath, (err, stats) => {

src/client/common/platform/platformService.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import * as os from 'os';
77
import { coerce, SemVer } from 'semver';
88
import { sendTelemetryEvent } from '../../telemetry';
99
import { PLATFORM_INFO, PlatformErrors } from '../../telemetry/constants';
10-
import { traceDecorators, traceError } from '../logger';
1110
import { OSType } from '../utils/platform';
1211
import { parseVersion } from '../utils/version';
1312
import { NON_WINDOWS_PATH_VARIABLE_NAME, WINDOWS_PATH_VARIABLE_NAME } from './constants';
@@ -23,7 +22,6 @@ export class PlatformService implements IPlatformService {
2322
public get virtualEnvBinName() {
2423
return this.isWindows ? 'Scripts' : 'bin';
2524
}
26-
@traceDecorators.verbose('Get Platform Version')
2725
public async getVersion(): Promise<SemVer> {
2826
if (this.version) {
2927
return this.version;
@@ -43,7 +41,6 @@ export class PlatformService implements IPlatformService {
4341
throw new Error('Unable to parse version');
4442
} catch (ex) {
4543
sendTelemetryEvent(PLATFORM_INFO, undefined, { failureType: PlatformErrors.FailedToParseVersion });
46-
traceError(`Failed to parse Version ${os.release()}`, ex);
4744
return parseVersion(os.release());
4845
}
4946
default:

src/client/common/process/proc.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
import { exec, spawn } from 'child_process';
44
import { Observable } from 'rxjs/Observable';
55
import * as tk from 'tree-kill';
6-
import { Disposable } from 'vscode';
7-
6+
import { IDisposable } from '../types';
87
import { createDeferred } from '../utils/async';
98
import { EnvironmentVariables } from '../variables/types';
109
import { DEFAULT_ENCODING } from './constants';
@@ -47,11 +46,11 @@ export class ProcessService implements IProcessService {
4746
let procExited = false;
4847

4948
const output = new Observable<Output<string>>(subscriber => {
50-
const disposables: Disposable[] = [];
49+
const disposables: IDisposable[] = [];
5150

5251
const on = (ee: NodeJS.EventEmitter, name: string, fn: Function) => {
5352
ee.on(name, fn as any);
54-
disposables.push({ dispose: () => ee.removeListener(name, fn as any) });
53+
disposables.push({ dispose: () => ee.removeListener(name, fn as any) as any });
5554
};
5655

5756
if (options.token) {
@@ -102,11 +101,11 @@ export class ProcessService implements IProcessService {
102101
const encoding = spawnOptions.encoding ? spawnOptions.encoding : 'utf8';
103102
const proc = spawn(file, args, spawnOptions);
104103
const deferred = createDeferred<ExecutionResult<string>>();
105-
const disposables: Disposable[] = [];
104+
const disposables: IDisposable[] = [];
106105

107106
const on = (ee: NodeJS.EventEmitter, name: string, fn: Function) => {
108107
ee.on(name, fn as any);
109-
disposables.push({ dispose: () => ee.removeListener(name, fn as any) });
108+
disposables.push({ dispose: () => ee.removeListener(name, fn as any) as any});
110109
};
111110

112111
if (options.token) {

src/client/common/process/pythonExecutionFactory.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,37 @@
22
// Licensed under the MIT License.
33

44
import { inject, injectable } from 'inversify';
5-
import { Uri } from 'vscode';
5+
import { IEnvironmentActivationService } from '../../interpreter/activation/types';
66
import { IServiceContainer } from '../../ioc/types';
7-
import { IConfigurationService } from '../types';
7+
import { sendTelemetryEvent } from '../../telemetry';
8+
import { PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES } from '../../telemetry/constants';
9+
import { IConfigurationService, Resource } from '../types';
10+
import { ProcessService } from './proc';
811
import { PythonExecutionService } from './pythonProcess';
9-
import { ExecutionFactoryCreationOptions, IProcessServiceFactory, IPythonExecutionFactory, IPythonExecutionService } from './types';
12+
import { ExecutionFactoryCreationOptions, IBufferDecoder, IProcessServiceFactory, IPythonExecutionFactory, IPythonExecutionService } from './types';
1013

1114
@injectable()
1215
export class PythonExecutionFactory implements IPythonExecutionFactory {
13-
private readonly configService: IConfigurationService;
14-
private processServiceFactory: IProcessServiceFactory;
15-
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
16-
this.processServiceFactory = serviceContainer.get<IProcessServiceFactory>(IProcessServiceFactory);
17-
this.configService = serviceContainer.get<IConfigurationService>(IConfigurationService);
16+
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer,
17+
@inject(IEnvironmentActivationService) private readonly activationHelper: IEnvironmentActivationService,
18+
@inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory,
19+
@inject(IConfigurationService) private readonly configService: IConfigurationService,
20+
@inject(IBufferDecoder) private readonly decoder: IBufferDecoder) {
1821
}
1922
public async create(options: ExecutionFactoryCreationOptions): Promise<IPythonExecutionService> {
2023
const pythonPath = options.pythonPath ? options.pythonPath : this.configService.getSettings(options.resource).pythonPath;
2124
const processService = await this.processServiceFactory.create(options.resource);
2225
return new PythonExecutionService(this.serviceContainer, processService, pythonPath);
2326
}
27+
public async createActivatedEnvironment(resource: Resource): Promise<IPythonExecutionService> {
28+
const envVars = await this.activationHelper.getActivatedEnvironmentVariables(resource);
29+
const hasEnvVars = envVars && Object.keys(envVars).length > 0;
30+
sendTelemetryEvent(PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES, undefined, { hasEnvVars });
31+
if (!hasEnvVars) {
32+
return this.create({ resource });
33+
}
34+
const pythonPath = this.configService.getSettings(resource).pythonPath;
35+
const processService = new ProcessService(this.decoder, { ...envVars });
36+
return new PythonExecutionService(this.serviceContainer, processService, pythonPath);
37+
}
2438
}

src/client/common/process/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { ChildProcess, ExecOptions, SpawnOptions as ChildProcessSpawnOptions } from 'child_process';
44
import { Observable } from 'rxjs/Observable';
55
import { CancellationToken, Uri } from 'vscode';
6-
import { ExecutionInfo, Version } from '../types';
6+
import { ExecutionInfo, Resource, Version } from '../types';
77
import { Architecture } from '../utils/platform';
88
import { EnvironmentVariables } from '../variables/types';
99

@@ -57,6 +57,7 @@ export type ExecutionFactoryCreationOptions = {
5757
};
5858
export interface IPythonExecutionFactory {
5959
create(options: ExecutionFactoryCreationOptions): Promise<IPythonExecutionService>;
60+
createActivatedEnvironment(resource: Resource): Promise<IPythonExecutionService>;
6061
}
6162
export type ReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final' | 'unknown';
6263
export type PythonVersionInfo = [number, number, number, ReleaseLevel];

src/client/common/serviceRegistry.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import { TerminalActivator } from './terminal/activator';
3737
import { PowershellTerminalActivationFailedHandler } from './terminal/activator/powershellFailedHandler';
3838
import { Bash } from './terminal/environmentActivationProviders/bash';
3939
import { CommandPromptAndPowerShell } from './terminal/environmentActivationProviders/commandPrompt';
40+
import { CondaActivationCommandProvider } from './terminal/environmentActivationProviders/condaActivationProvider';
41+
import { PipEnvActivationCommandProvider } from './terminal/environmentActivationProviders/pipEnvActivationProvider';
4042
import { PyEnvActivationCommandProvider } from './terminal/environmentActivationProviders/pyenvActivationProvider';
4143
import { TerminalServiceFactory } from './terminal/factory';
4244
import { TerminalHelper } from './terminal/helper';
@@ -45,7 +47,8 @@ import {
4547
ITerminalActivationHandler,
4648
ITerminalActivator,
4749
ITerminalHelper,
48-
ITerminalServiceFactory
50+
ITerminalServiceFactory,
51+
TerminalActivationProviders
4952
} from './terminal/types';
5053
import {
5154
IAsyncDisposableRegistry,
@@ -93,11 +96,15 @@ export function registerTypes(serviceManager: IServiceManager) {
9396

9497
serviceManager.addSingleton<ITerminalHelper>(ITerminalHelper, TerminalHelper);
9598
serviceManager.addSingleton<ITerminalActivationCommandProvider>(
96-
ITerminalActivationCommandProvider, Bash, 'bashCShellFish');
99+
ITerminalActivationCommandProvider, Bash, TerminalActivationProviders.bashCShellFish);
97100
serviceManager.addSingleton<ITerminalActivationCommandProvider>(
98-
ITerminalActivationCommandProvider, CommandPromptAndPowerShell, 'commandPromptAndPowerShell');
101+
ITerminalActivationCommandProvider, CommandPromptAndPowerShell, TerminalActivationProviders.commandPromptAndPowerShell);
99102
serviceManager.addSingleton<ITerminalActivationCommandProvider>(
100-
ITerminalActivationCommandProvider, PyEnvActivationCommandProvider, 'pyenv');
103+
ITerminalActivationCommandProvider, PyEnvActivationCommandProvider, TerminalActivationProviders.pyenv);
104+
serviceManager.addSingleton<ITerminalActivationCommandProvider>(
105+
ITerminalActivationCommandProvider, CondaActivationCommandProvider, TerminalActivationProviders.conda);
106+
serviceManager.addSingleton<ITerminalActivationCommandProvider>(
107+
ITerminalActivationCommandProvider, PipEnvActivationCommandProvider, TerminalActivationProviders.pipenv);
101108
serviceManager.addSingleton<IFeatureDeprecationManager>(IFeatureDeprecationManager, FeatureDeprecationManager);
102109

103110
serviceManager.addSingleton<IAsyncDisposableRegistry>(IAsyncDisposableRegistry, AsyncDisposableRegistry);

src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
'use strict';
55

6-
import { injectable } from 'inversify';
6+
import { inject, injectable } from 'inversify';
77
import * as path from 'path';
88
import { Uri } from 'vscode';
99
import { ICondaService } from '../../../interpreter/contracts';
@@ -19,7 +19,7 @@ import { ITerminalActivationCommandProvider, TerminalShellType } from '../types'
1919
@injectable()
2020
export class CondaActivationCommandProvider implements ITerminalActivationCommandProvider {
2121
constructor(
22-
private readonly serviceContainer: IServiceContainer
22+
@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer
2323
) { }
2424

2525
/**
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { inject, injectable } from 'inversify';
7+
import { Uri } from 'vscode';
8+
import { IInterpreterService, InterpreterType } from '../../../interpreter/contracts';
9+
import { ITerminalActivationCommandProvider, TerminalShellType } from '../types';
10+
11+
@injectable()
12+
export class PipEnvActivationCommandProvider implements ITerminalActivationCommandProvider {
13+
constructor(@inject(IInterpreterService) private readonly interpreterService: IInterpreterService) { }
14+
15+
public isShellSupported(_targetShell: TerminalShellType): boolean {
16+
return true;
17+
}
18+
19+
public async getActivationCommands(resource: Uri | undefined, _: TerminalShellType): Promise<string[] | undefined> {
20+
const interpreter = await this.interpreterService.getActiveInterpreter(resource);
21+
if (!interpreter || interpreter.type !== InterpreterType.Pipenv) {
22+
return;
23+
}
24+
25+
return ['pipenv shell'];
26+
}
27+
}

0 commit comments

Comments
 (0)