Skip to content

Commit 033c3ff

Browse files
authored
Merge branch 'main' into fix-environments-extension-scope-activation
2 parents f938935 + e0f1d3c commit 033c3ff

5 files changed

Lines changed: 354 additions & 54 deletions

File tree

src/client/envExt/api.legacy.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import { getEnvExtApi, getEnvironment } from './api.internal';
66
import { EnvironmentType, PythonEnvironment as PythonEnvironmentLegacy } from '../pythonEnvironments/info';
77
import { PythonEnvironment, PythonTerminalCreateOptions } from './types';
88
import { Architecture } from '../common/utils/platform';
9-
import { parseVersion } from '../pythonEnvironments/base/info/pythonVersion';
109
import { PythonEnvType } from '../pythonEnvironments/base/info';
1110
import { traceError } from '../logging';
1211
import { reportActiveInterpreterChanged } from '../environmentApi';
1312
import { getWorkspaceFolder, getWorkspaceFolders } from '../common/vscodeApis/workspaceApis';
13+
import { parsePythonEnvironmentVersion } from './utils';
1414

1515
function toEnvironmentType(pythonEnv: PythonEnvironment): EnvironmentType {
1616
if (pythonEnv.envId.managerId.toLowerCase().endsWith('system')) {
@@ -73,8 +73,11 @@ function getEnvType(kind: EnvironmentType): PythonEnvType | undefined {
7373
}
7474
}
7575

76-
function toLegacyType(env: PythonEnvironment): PythonEnvironmentLegacy {
77-
const ver = parseVersion(env.version);
76+
function toLegacyType(env: PythonEnvironment): PythonEnvironmentLegacy | undefined {
77+
const ver = parsePythonEnvironmentVersion(env);
78+
if (!ver) {
79+
return undefined;
80+
}
7881
const envType = toEnvironmentType(env);
7982
return {
8083
id: env.execInfo.run.executable,

src/client/envExt/envExtApi.ts

Lines changed: 73 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import * as path from 'path';
66
import { Event, EventEmitter, Disposable, Uri } from 'vscode';
7-
import { PythonEnvInfo, PythonEnvKind, PythonEnvType, PythonVersion } from '../pythonEnvironments/base/info';
7+
import { PythonEnvInfo, PythonEnvKind, PythonEnvType } from '../pythonEnvironments/base/info';
88
import {
99
GetRefreshEnvironmentsOptions,
1010
IDiscoveryAPI,
@@ -26,8 +26,8 @@ import {
2626
} from './types';
2727
import { FileChangeType } from '../common/platform/fileSystemWatcher';
2828
import { Architecture, isWindows } from '../common/utils/platform';
29-
import { parseVersion } from '../pythonEnvironments/base/info/pythonVersion';
3029
import { Interpreters } from '../common/utils/localize';
30+
import { parsePythonEnvironmentVersion } from './utils';
3131

3232
function getKind(pythonEnv: PythonEnvironment): PythonEnvKind {
3333
if (pythonEnv.envId.managerId.toLowerCase().endsWith('system')) {
@@ -127,39 +127,51 @@ function getEnvType(kind: PythonEnvKind): PythonEnvType | undefined {
127127
}
128128

129129
function toPythonEnvInfo(pythonEnv: PythonEnvironment): PythonEnvInfo | undefined {
130-
const kind = getKind(pythonEnv);
131-
const arch = Architecture.x64;
132-
const version: PythonVersion = parseVersion(pythonEnv.version);
133-
const { name, displayName, sysPrefix } = pythonEnv;
134-
const executable = getExecutable(pythonEnv);
135-
const location = getLocation(pythonEnv);
136-
137-
return {
138-
name,
139-
location,
140-
kind,
141-
id: executable,
142-
executable: {
143-
filename: executable,
144-
sysPrefix,
145-
ctime: -1,
146-
mtime: -1,
147-
},
148-
version: {
149-
sysVersion: pythonEnv.version,
150-
major: version.major,
151-
minor: version.minor,
152-
micro: version.micro,
153-
},
154-
arch,
155-
distro: {
156-
org: '',
157-
},
158-
source: [],
159-
detailedDisplayName: displayName,
160-
display: displayName,
161-
type: getEnvType(kind),
162-
};
130+
const version = parsePythonEnvironmentVersion(pythonEnv);
131+
if (!version) {
132+
return undefined;
133+
}
134+
135+
try {
136+
const kind = getKind(pythonEnv);
137+
const arch = Architecture.x64;
138+
const { name, displayName, sysPrefix } = pythonEnv;
139+
const executable = getExecutable(pythonEnv);
140+
const location = getLocation(pythonEnv);
141+
142+
return {
143+
name,
144+
location,
145+
kind,
146+
id: executable,
147+
executable: {
148+
filename: executable,
149+
sysPrefix,
150+
ctime: -1,
151+
mtime: -1,
152+
},
153+
version: {
154+
sysVersion: pythonEnv.version,
155+
major: version.major,
156+
minor: version.minor,
157+
micro: version.micro,
158+
},
159+
arch,
160+
distro: {
161+
org: '',
162+
},
163+
source: [],
164+
detailedDisplayName: displayName,
165+
display: displayName,
166+
type: getEnvType(kind),
167+
};
168+
} catch (error) {
169+
traceError(
170+
`Failed to convert environment "${pythonEnv.displayName}" from the Python Environments extension`,
171+
error,
172+
);
173+
return undefined;
174+
}
163175
}
164176

165177
function hasChanged(old: PythonEnvInfo, newEnv: PythonEnvInfo): boolean {
@@ -214,11 +226,16 @@ class EnvExtApis implements IDiscoveryAPI, Disposable {
214226
this._onChanged,
215227
this.envExtApi.onDidChangeEnvironments((e) => this.onDidChangeEnvironments(e)),
216228
this.envExtApi.onDidChangeEnvironment((e) => {
229+
const oldEnv = e.old ? toPythonEnvInfo(e.old) : undefined;
230+
const newEnv = e.new ? toPythonEnvInfo(e.new) : undefined;
231+
if ((e.old && !oldEnv) || (e.new && !newEnv)) {
232+
return;
233+
}
217234
this._onChanged.fire({
218235
type: FileChangeType.Changed,
219236
searchLocation: e.uri,
220-
old: e.old ? toPythonEnvInfo(e.old) : undefined,
221-
new: e.new ? toPythonEnvInfo(e.new) : undefined,
237+
old: oldEnv,
238+
new: newEnv,
222239
});
223240
}),
224241
);
@@ -293,15 +310,11 @@ class EnvExtApis implements IDiscoveryAPI, Disposable {
293310
return info;
294311
}
295312

296-
private removeEnv(env: PythonEnvInfo | string): void {
297-
if (typeof env === 'string') {
298-
const old = this._envs.find((item) => item.executable.filename === env);
299-
this._envs = this._envs.filter((item) => item.executable.filename !== env);
300-
this._onChanged.fire({ type: FileChangeType.Deleted, old });
301-
return;
302-
}
303-
this._envs = this._envs.filter((item) => item.executable.filename !== env.executable.filename);
304-
this._onChanged.fire({ type: FileChangeType.Deleted, old: env });
313+
private removeEnv(env: PythonEnvironment): void {
314+
const executable = getExecutable(env);
315+
const old = this._envs.find((item) => item.executable.filename === executable);
316+
this._envs = this._envs.filter((item) => item.executable.filename !== executable);
317+
this._onChanged.fire({ type: FileChangeType.Deleted, old });
305318
}
306319

307320
async resolveEnv(envPath?: string): Promise<PythonEnvInfo | undefined> {
@@ -328,11 +341,20 @@ class EnvExtApis implements IDiscoveryAPI, Disposable {
328341

329342
onDidChangeEnvironments(e: DidChangeEnvironmentsEventArgs): void {
330343
e.forEach((item) => {
331-
if (item.kind === EnvironmentChangeKind.remove) {
332-
this.removeEnv(item.environment.environmentPath.fsPath);
333-
}
334-
if (item.kind === EnvironmentChangeKind.add) {
335-
this.addEnv(item.environment);
344+
try {
345+
if (item.kind === EnvironmentChangeKind.remove) {
346+
this.removeEnv(item.environment);
347+
}
348+
if (item.kind === EnvironmentChangeKind.add) {
349+
this.addEnv(item.environment);
350+
}
351+
} catch (error) {
352+
traceError(
353+
`Failed to process environment change for "${
354+
item?.environment?.displayName ?? 'unknown environment'
355+
}" from the Python Environments extension`,
356+
error,
357+
);
336358
}
337359
});
338360
}

src/client/envExt/utils.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { traceError, traceVerbose } from '../logging';
5+
import { PythonVersion } from '../pythonEnvironments/base/info';
6+
import { parseVersion } from '../pythonEnvironments/base/info/pythonVersion';
7+
import { PythonEnvironment } from './types';
8+
9+
export function parsePythonEnvironmentVersion(pythonEnv: PythonEnvironment): PythonVersion | undefined {
10+
if (pythonEnv.version === 'no-python') {
11+
traceVerbose(`Skipping environment without Python: ${pythonEnv.displayName}`);
12+
return undefined;
13+
}
14+
15+
try {
16+
return parseVersion(pythonEnv.version);
17+
} catch (error) {
18+
traceError(
19+
`Failed to parse version for environment "${pythonEnv.displayName}" from the Python Environments extension`,
20+
error,
21+
);
22+
return undefined;
23+
}
24+
}

src/test/envExt/api.legacy.unit.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,20 @@ suite('Env extension legacy API - getActiveInterpreterLegacy', () => {
7272

7373
expect(getEnvironmentStub.callCount).to.equal(2);
7474
});
75+
76+
test('Returns undefined for an environment without Python', async () => {
77+
getEnvironmentStub.resolves(buildEnv('/usr/bin/conda', 'no-python'));
78+
79+
const result = await getActiveInterpreterLegacy(undefined);
80+
81+
expect(result).to.equal(undefined);
82+
});
83+
84+
test('Returns undefined for an environment with an invalid version', async () => {
85+
getEnvironmentStub.resolves(buildEnv('/usr/bin/python', 'not-a-version'));
86+
87+
const result = await getActiveInterpreterLegacy(undefined);
88+
89+
expect(result).to.equal(undefined);
90+
});
7591
});

0 commit comments

Comments
 (0)