Skip to content

Commit d95dc03

Browse files
authored
Use activation (microsoft#2788)
1 parent 428a349 commit d95dc03

11 files changed

Lines changed: 634 additions & 254 deletions

File tree

src/client/application/diagnostics/checks/pythonInterpreter.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
'use strict';
55

66
import { inject, injectable } from 'inversify';
7-
import { DiagnosticSeverity } from 'vscode';
7+
import { ConfigurationChangeEvent, DiagnosticSeverity, Uri } from 'vscode';
8+
import { IWorkspaceService } from '../../../common/application/types';
89
import '../../../common/extensions';
910
import { IPlatformService } from '../../../common/platform/types';
10-
import { IConfigurationService } from '../../../common/types';
11+
import { IConfigurationService, IDisposableRegistry } from '../../../common/types';
1112
import { IInterpreterHelper, IInterpreterService, InterpreterType } from '../../../interpreter/contracts';
1213
import { IServiceContainer } from '../../../ioc/types';
1314
import { BaseDiagnostic, BaseDiagnosticsService } from '../base';
@@ -32,10 +33,13 @@ export const InvalidPythonInterpreterServiceId = 'InvalidPythonInterpreterServic
3233

3334
@injectable()
3435
export class InvalidPythonInterpreterService extends BaseDiagnosticsService {
36+
protected changeThrottleTimeout = 1000;
37+
private timeOut?: NodeJS.Timer;
3538
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
3639
super([DiagnosticCodes.NoPythonInterpretersDiagnostic,
3740
DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic,
3841
DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic], serviceContainer);
42+
this.addPythonPathChangedHandler();
3943
}
4044
public async diagnose(): Promise<IDiagnostic[]> {
4145
const configurationService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
@@ -83,6 +87,27 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService {
8387
return messageService.handle(diagnostic, { commandPrompts, message: diagnostic.message });
8488
}));
8589
}
90+
protected addPythonPathChangedHandler() {
91+
const workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
92+
const disposables = this.serviceContainer.get<IDisposableRegistry>(IDisposableRegistry);
93+
disposables.push(workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)));
94+
}
95+
protected async onDidChangeConfiguration(event: ConfigurationChangeEvent) {
96+
const workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
97+
const workspacesUris: (Uri | undefined)[] = workspaceService.hasWorkspaceFolders ? workspaceService.workspaceFolders!.map(workspace => workspace.uri) : [undefined];
98+
if (workspacesUris.findIndex(uri => event.affectsConfiguration('python.pythonPath', uri)) === -1) {
99+
return;
100+
}
101+
// Lets wait, for more changes, dirty simple throttling.
102+
if (this.timeOut) {
103+
clearTimeout(this.timeOut);
104+
this.timeOut = undefined;
105+
}
106+
this.timeOut = setTimeout(() => {
107+
this.timeOut = undefined;
108+
this.diagnose().then(dianostics => this.handle(dianostics)).ignoreErrors();
109+
}, this.changeThrottleTimeout);
110+
}
86111
private getCommandPrompts(diagnostic: IDiagnostic): { prompt: string; command?: IDiagnosticCommand }[] {
87112
const commandFactory = this.serviceContainer.get<IDiagnosticsCommandFactory>(IDiagnosticsCommandFactory);
88113
switch (diagnostic.code) {

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ export abstract class BaseActivationCommandProvider implements ITerminalActivati
1414
constructor(protected readonly serviceContainer: IServiceContainer) { }
1515

1616
public abstract isShellSupported(targetShell: TerminalShellType): boolean;
17-
public abstract getActivationCommands(resource: Uri | undefined, targetShell: TerminalShellType): Promise<string[] | undefined>;
18-
19-
protected async findScriptFile(resource: Uri | undefined, scriptFileNames: string[]): Promise<string | undefined> {
20-
const fs = this.serviceContainer.get<IFileSystem>(IFileSystem);
17+
public getActivationCommands(resource: Uri | undefined, targetShell: TerminalShellType): Promise<string[] | undefined> {
2118
const pythonPath = this.serviceContainer.get<IConfigurationService>(IConfigurationService).getSettings(resource).pythonPath;
19+
return this.getActivationCommandsForInterpreter(pythonPath, targetShell);
20+
}
21+
public abstract getActivationCommandsForInterpreter(pythonPath: string, targetShell: TerminalShellType): Promise<string[] | undefined>;
2222

23+
protected async findScriptFile(pythonPath: string, scriptFileNames: string[]): Promise<string | undefined> {
24+
const fs = this.serviceContainer.get<IFileSystem>(IFileSystem);
2325
for (const scriptFileName of scriptFileNames) {
2426
// Generate scripts are found in the same directory as the interpreter.
2527
const scriptFile = path.join(path.dirname(pythonPath), scriptFileName);

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

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

44
import { inject, injectable } from 'inversify';
5-
import { Uri } from 'vscode';
65
import { IServiceContainer } from '../../../ioc/types';
76
import '../../extensions';
87
import { TerminalShellType } from '../types';
@@ -23,8 +22,8 @@ export class Bash extends BaseActivationCommandProvider {
2322
targetShell === TerminalShellType.tcshell ||
2423
targetShell === TerminalShellType.fish;
2524
}
26-
public async getActivationCommands(resource: Uri | undefined, targetShell: TerminalShellType): Promise<string[] | undefined> {
27-
const scriptFile = await this.findScriptFile(resource, this.getScriptsInOrderOfPreference(targetShell));
25+
public async getActivationCommandsForInterpreter(pythonPath: string, targetShell: TerminalShellType): Promise<string[] | undefined> {
26+
const scriptFile = await this.findScriptFile(pythonPath, this.getScriptsInOrderOfPreference(targetShell));
2827
if (!scriptFile) {
2928
return;
3029
}

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

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

44
import { inject, injectable } from 'inversify';
55
import * as path from 'path';
6-
import { Uri } from 'vscode';
76
import { IServiceContainer } from '../../../ioc/types';
87
import '../../extensions';
98
import { IPlatformService } from '../../platform/types';
@@ -20,9 +19,9 @@ export class CommandPromptAndPowerShell extends BaseActivationCommandProvider {
2019
targetShell === TerminalShellType.powershell ||
2120
targetShell === TerminalShellType.powershellCore;
2221
}
23-
public async getActivationCommands(resource: Uri | undefined, targetShell: TerminalShellType): Promise<string[] | undefined> {
22+
public async getActivationCommandsForInterpreter(pythonPath, targetShell: TerminalShellType): Promise<string[] | undefined> {
2423
// Dependending on the target shell, look for the preferred script file.
25-
const scriptFile = await this.findScriptFile(resource, this.getScriptsInOrderOfPreference(targetShell));
24+
const scriptFile = await this.findScriptFile(pythonPath, this.getScriptsInOrderOfPreference(targetShell));
2625
if (!scriptFile) {
2726
return;
2827
}

src/client/common/terminal/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,5 @@ export const ITerminalActivationCommandProvider = Symbol('ITerminalActivationCom
5757
export interface ITerminalActivationCommandProvider {
5858
isShellSupported(targetShell: TerminalShellType): boolean;
5959
getActivationCommands(resource: Uri | undefined, targetShell: TerminalShellType): Promise<string[] | undefined>;
60+
getActivationCommandsForInterpreter?(pythonPath, targetShell: TerminalShellType): Promise<string[] | undefined>;
6061
}

src/client/interpreter/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class InterpreterHelper implements IInterpreterHelper {
4646
public async getInterpreterInformation(pythonPath: string): Promise<undefined | Partial<PythonInterpreter>> {
4747
let fileHash = await this.fs.getFileHash(pythonPath).catch(() => '');
4848
fileHash = fileHash ? fileHash : '';
49-
const store = this.persistentFactory.createGlobalPersistentState<CachedPythonInterpreter>(`${pythonPath}.v1`, undefined, EXPITY_DURATION);
49+
const store = this.persistentFactory.createGlobalPersistentState<CachedPythonInterpreter>(`${pythonPath}.v2`, undefined, EXPITY_DURATION);
5050
if (store.value && fileHash && store.value.fileHash === fileHash) {
5151
return store.value;
5252
}

src/client/interpreter/interpreterService.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export class InterpreterService implements Disposable, IInterpreterService {
127127

128128
let fileHash = await this.fs.getFileHash(pythonPath).catch(() => '');
129129
fileHash = fileHash ? fileHash : '';
130-
const store = this.persistentStateFactory.createGlobalPersistentState<PythonInterpreter & { fileHash: string }>(`${pythonPath}.interpreter.details.v2`, undefined, EXPITY_DURATION);
130+
const store = this.persistentStateFactory.createGlobalPersistentState<PythonInterpreter & { fileHash: string }>(`${pythonPath}.interpreter.details.v5`, undefined, EXPITY_DURATION);
131131
if (store.value && fileHash && store.value.fileHash === fileHash) {
132132
return store.value;
133133
}
@@ -151,10 +151,10 @@ export class InterpreterService implements Disposable, IInterpreterService {
151151
type: type
152152
};
153153

154-
const virtualEnvName = await virtualEnvManager.getEnvironmentName(pythonPath, resource);
154+
const envName = type === InterpreterType.Unknown ? undefined : await virtualEnvManager.getEnvironmentName(pythonPath, resource);
155155
interpreterInfo = {
156156
...(details as PythonInterpreter),
157-
envName: virtualEnvName
157+
envName
158158
};
159159
interpreterInfo.displayName = await this.getDisplayName(interpreterInfo, resource);
160160
}
@@ -172,7 +172,7 @@ export class InterpreterService implements Disposable, IInterpreterService {
172172
* @memberof InterpreterService
173173
*/
174174
public async getDisplayName(info: Partial<PythonInterpreter>, resource?: Uri): Promise<string> {
175-
const store = this.persistentStateFactory.createGlobalPersistentState<string>(`${info.path}.interpreter.displayName.v3`, undefined, EXPITY_DURATION);
175+
const store = this.persistentStateFactory.createGlobalPersistentState<string>(`${info.path}.interpreter.displayName.v5`, undefined, EXPITY_DURATION);
176176
if (store.value) {
177177
return store.value;
178178
}

src/client/interpreter/locators/services/cacheableLocatorService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ
1919
constructor(@unmanaged() name: string,
2020
@unmanaged() protected readonly serviceContainer: IServiceContainer,
2121
@unmanaged() private cachePerWorkspace: boolean = false) {
22-
this.cacheKeyPrefix = `INTERPRETERS_CACHE_v1_${name}`;
22+
this.cacheKeyPrefix = `INTERPRETERS_CACHE_v2_${name}`;
2323
}
2424
public abstract dispose();
2525
public async getInterpreters(resource?: Uri): Promise<PythonInterpreter[]> {

src/client/interpreter/virtualEnvs/index.ts

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
import { inject, injectable } from 'inversify';
55
import * as path from 'path';
66
import { Uri } from 'vscode';
7+
import { getNamesAndValues } from '../../../utils/enum';
78
import { noop } from '../../../utils/misc';
89
import { IWorkspaceService } from '../../common/application/types';
9-
import { IFileSystem } from '../../common/platform/types';
10+
import { IFileSystem, IPlatformService } from '../../common/platform/types';
1011
import { IProcessServiceFactory } from '../../common/process/types';
12+
import { ITerminalActivationCommandProvider, TerminalShellType } from '../../common/terminal/types';
1113
import { ICurrentProcess, IPathUtils } from '../../common/types';
1214
import { IServiceContainer } from '../../ioc/types';
1315
import { InterpreterType, IPipEnvService } from '../contracts';
@@ -41,33 +43,48 @@ export class VirtualEnvironmentManager implements IVirtualEnvironmentManager {
4143
return grandParentDirName;
4244
}
4345
public async getEnvironmentType(pythonPath: string, resource?: Uri): Promise<InterpreterType> {
44-
const dir = path.dirname(pythonPath);
45-
const pyEnvCfgFiles = PYENVFILES.map(file => path.join(dir, file));
46-
for (const file of pyEnvCfgFiles) {
47-
if (await this.fs.fileExists(file)) {
48-
return InterpreterType.Venv;
49-
}
46+
if (await this.isVenvEnvironment(pythonPath)) {
47+
return InterpreterType.Venv;
5048
}
5149

52-
const pyEnvRoot = await this.getPyEnvRoot(resource);
53-
if (pyEnvRoot && pythonPath.startsWith(pyEnvRoot)) {
50+
if (await this.isPyEnvEnvironment(pythonPath, resource)) {
5451
return InterpreterType.Pyenv;
5552
}
5653

57-
const defaultWorkspaceUri = this.workspaceService.hasWorkspaceFolders ? this.workspaceService.workspaceFolders![0].uri : undefined;
58-
const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined;
59-
const workspaceUri = workspaceFolder ? workspaceFolder.uri : defaultWorkspaceUri;
60-
if (workspaceUri && await this.pipEnvService.isRelatedPipEnvironment(workspaceUri.fsPath, pythonPath)) {
54+
if (await this.isPipEnvironment(pythonPath, resource)) {
6155
return InterpreterType.PipEnv;
6256
}
6357

64-
if ((await this.getEnvironmentName(pythonPath)).length > 0) {
58+
if (await this.isVirtualEnvironment(pythonPath)) {
6559
return InterpreterType.VirtualEnv;
6660
}
6761

6862
// Lets not try to determine whether this is a conda environment or not.
6963
return InterpreterType.Unknown;
7064
}
65+
public async isVenvEnvironment(pythonPath: string) {
66+
const dir = path.dirname(pythonPath);
67+
const pyEnvCfgFiles = PYENVFILES.map(file => path.join(dir, file));
68+
for (const file of pyEnvCfgFiles) {
69+
if (await this.fs.fileExists(file)) {
70+
return true;
71+
}
72+
}
73+
return false;
74+
}
75+
public async isPyEnvEnvironment(pythonPath: string, resource?: Uri) {
76+
const pyEnvRoot = await this.getPyEnvRoot(resource);
77+
return pyEnvRoot && pythonPath.startsWith(pyEnvRoot);
78+
}
79+
public async isPipEnvironment(pythonPath: string, resource?: Uri) {
80+
const defaultWorkspaceUri = this.workspaceService.hasWorkspaceFolders ? this.workspaceService.workspaceFolders![0].uri : undefined;
81+
const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined;
82+
const workspaceUri = workspaceFolder ? workspaceFolder.uri : defaultWorkspaceUri;
83+
if (workspaceUri && await this.pipEnvService.isRelatedPipEnvironment(workspaceUri.fsPath, pythonPath)) {
84+
return true;
85+
}
86+
return false;
87+
}
7188
public async getPyEnvRoot(resource?: Uri): Promise<string | undefined> {
7289
if (this.pyEnvRoot) {
7390
return this.pyEnvRoot;
@@ -91,4 +108,24 @@ export class VirtualEnvironmentManager implements IVirtualEnvironmentManager {
91108
const pathUtils = this.serviceContainer.get<IPathUtils>(IPathUtils);
92109
return this.pyEnvRoot = path.join(pathUtils.home, '.pyenv');
93110
}
111+
public async isVirtualEnvironment(pythonPath: string) {
112+
const provider = this.getTerminalActivationProviderForVirtualEnvs();
113+
const shells = getNamesAndValues<TerminalShellType>(TerminalShellType)
114+
.filter(shell => provider.isShellSupported(shell.value))
115+
.map(shell => shell.value);
116+
117+
for (const shell of shells) {
118+
const cmds = await provider.getActivationCommandsForInterpreter!(pythonPath, shell);
119+
if (cmds && cmds.length > 0) {
120+
return true;
121+
}
122+
}
123+
124+
return false;
125+
}
126+
private getTerminalActivationProviderForVirtualEnvs(): ITerminalActivationCommandProvider {
127+
const isWindows = this.serviceContainer.get<IPlatformService>(IPlatformService).isWindows;
128+
const serviceName = isWindows ? 'commandPromptAndPowerShell' : 'bashCShellFish';
129+
return this.serviceContainer.get<ITerminalActivationCommandProvider>(ITerminalActivationCommandProvider, serviceName);
130+
}
94131
}

0 commit comments

Comments
 (0)