Skip to content

Commit 1ae84e9

Browse files
author
Kartik Raj
authored
Assume the executable for conda envs lacking an interpreter to be python (#19249)
* Assume the executable for conda envs lacking an interpreter to be python * Add tests * Add more tests * News
1 parent 3d8c333 commit 1ae84e9

File tree

7 files changed

+54
-38
lines changed

7 files changed

+54
-38
lines changed

news/1 Enhancements/18934.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ensure conda envs lacking an interpreter which do not use a valid python binary are also discovered and is selectable, so that `conda env list` matches with what the extension reports.

src/client/pythonEnvironments/base/info/env.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -251,12 +251,18 @@ export function areSameEnv(
251251
return true;
252252
}
253253

254-
if (allowPartialMatch && arePathsSame(path.dirname(leftFilename), path.dirname(rightFilename))) {
255-
const leftVersion = typeof left === 'string' ? undefined : leftInfo.version;
256-
const rightVersion = typeof right === 'string' ? undefined : rightInfo.version;
257-
if (leftVersion && rightVersion) {
258-
if (areIdenticalVersion(leftVersion, rightVersion) || areSimilarVersions(leftVersion, rightVersion)) {
259-
return true;
254+
if (allowPartialMatch) {
255+
const isSameDirectory =
256+
leftFilename !== 'python' &&
257+
rightFilename !== 'python' &&
258+
arePathsSame(path.dirname(leftFilename), path.dirname(rightFilename));
259+
if (isSameDirectory) {
260+
const leftVersion = typeof left === 'string' ? undefined : leftInfo.version;
261+
const rightVersion = typeof right === 'string' ? undefined : rightInfo.version;
262+
if (leftVersion && rightVersion) {
263+
if (areIdenticalVersion(leftVersion, rightVersion) || areSimilarVersions(leftVersion, rightVersion)) {
264+
return true;
265+
}
260266
}
261267
}
262268
}

src/client/pythonEnvironments/base/info/environmentInfoService.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { Conda, CONDA_ACTIVATION_TIMEOUT, isCondaEnvironment } from '../../commo
1111
import { PythonEnvInfo, PythonEnvKind } from '.';
1212
import { normCasePath } from '../../common/externalDependencies';
1313
import { OUTPUT_MARKER_SCRIPT } from '../../../common/process/internal/scripts';
14+
import { Architecture } from '../../../common/utils/platform';
15+
import { getEmptyVersion } from './pythonVersion';
1416

1517
export enum EnvironmentInfoServiceQueuePriority {
1618
Default,
@@ -100,6 +102,20 @@ class EnvironmentInfoService implements IEnvironmentInfoService {
100102
env: PythonEnvInfo,
101103
priority?: EnvironmentInfoServiceQueuePriority,
102104
): Promise<InterpreterInformation | undefined> {
105+
if (env.kind === PythonEnvKind.Conda && env.executable.filename === 'python') {
106+
const emptyInterpreterInfo: InterpreterInformation = {
107+
arch: Architecture.Unknown,
108+
executable: {
109+
filename: 'python',
110+
ctime: -1,
111+
mtime: -1,
112+
sysPrefix: '',
113+
},
114+
version: getEmptyVersion(),
115+
};
116+
117+
return emptyInterpreterInfo;
118+
}
103119
if (this.workerPool === undefined) {
104120
this.workerPool = createRunningWorkerPool<PythonEnvInfo, InterpreterInformation | undefined>(
105121
buildEnvironmentInfo,

src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ export class PythonEnvInfoCache extends PythonEnvsWatcher<PythonEnvCollectionCha
8686
* we avoid the cost of running lstat. So simply remove envs which no longer
8787
* exist.
8888
*/
89-
const areEnvsValid = await Promise.all(this.envs.map((e) => pathExists(e.executable.filename)));
89+
const areEnvsValid = await Promise.all(
90+
this.envs.map((e) => (e.executable.filename === 'python' ? true : pathExists(e.executable.filename))),
91+
);
9092
const invalidIndexes = areEnvsValid
9193
.map((isValid, index) => (isValid ? -1 : index))
9294
.filter((i) => i !== -1)

src/client/pythonEnvironments/common/environmentManagers/conda.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
isParentPath,
99
pathExists,
1010
readFile,
11-
shellExecute,
1211
onDidChangePythonSetting,
1312
exec,
1413
} from '../externalDependencies';
@@ -22,8 +21,6 @@ import { cache } from '../../../common/utils/decorators';
2221
import { isTestExecution } from '../../../common/constants';
2322
import { traceError, traceVerbose } from '../../../logging';
2423
import { OUTPUT_MARKER_SCRIPT } from '../../../common/process/internal/scripts';
25-
import { buildPythonExecInfo } from '../../exec';
26-
import { getExecutablePath } from '../../info/executable';
2724

2825
export const AnacondaCompanyName = 'Anaconda, Inc.';
2926
export const CONDAPATH_SETTING_KEY = 'condaPath';
@@ -483,6 +480,7 @@ export class Conda {
483480
/**
484481
* Returns executable associated with the conda env, swallows exceptions.
485482
*/
483+
// eslint-disable-next-line class-methods-use-this
486484
public async getInterpreterPathForEnvironment(condaEnv: CondaEnvInfo): Promise<string | undefined> {
487485
try {
488486
const executablePath = await getInterpreterPath(condaEnv.prefix);
@@ -491,26 +489,16 @@ export class Conda {
491489
return executablePath;
492490
}
493491
traceVerbose(
494-
'Executable does not exist within conda env, running conda run to get it',
492+
'Executable does not exist within conda env, assume the executable to be `python`',
495493
JSON.stringify(condaEnv),
496494
);
497-
return this.getInterpreterPathUsingCondaRun(condaEnv);
495+
return 'python';
498496
} catch (ex) {
499497
traceError(`Failed to get executable for conda env: ${JSON.stringify(condaEnv)}`, ex);
500498
return undefined;
501499
}
502500
}
503501

504-
@cache(-1, true)
505-
private async getInterpreterPathUsingCondaRun(condaEnv: CondaEnvInfo) {
506-
const runArgs = await this.getRunPythonArgs(condaEnv);
507-
if (runArgs) {
508-
const python = buildPythonExecInfo(runArgs);
509-
return getExecutablePath(python, shellExecute, CONDA_ACTIVATION_TIMEOUT);
510-
}
511-
return undefined;
512-
}
513-
514502
public async getRunPythonArgs(env: CondaEnvInfo, forShellExecution?: boolean): Promise<string[] | undefined> {
515503
const condaVersion = await this.getCondaVersion();
516504
if (condaVersion && lt(condaVersion, CONDA_RUN_VERSION)) {

src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ suite('Python envs locator - Environments Collection', async () => {
7878
path.join(TEST_LAYOUT_ROOT, 'pipenv', 'project1', '.venv', 'Scripts', 'python.exe'),
7979
Uri.file(TEST_LAYOUT_ROOT),
8080
);
81-
return [envCached1, envCached2];
81+
const envCached3 = createEnv('python');
82+
return [envCached1, envCached2, envCached3];
8283
}
8384

8485
function getCachedEnvs() {
@@ -89,6 +90,7 @@ suite('Python envs locator - Environments Collection', async () => {
8990
function getExpectedEnvs(doNotIncludeCached?: boolean) {
9091
const fakeLocalAppDataPath = path.join(TEST_LAYOUT_ROOT, 'storeApps');
9192
const envCached1 = createEnv(path.join(fakeLocalAppDataPath, 'Microsoft', 'WindowsApps', 'python.exe'));
93+
const envCached2 = createEnv('python');
9294
const env1 = createEnv(path.join(TEST_LAYOUT_ROOT, 'conda1', 'python.exe'), undefined, updatedName);
9395
const env2 = createEnv(
9496
path.join(TEST_LAYOUT_ROOT, 'pipenv', 'project1', '.venv', 'Scripts', 'python.exe'),
@@ -106,7 +108,7 @@ suite('Python envs locator - Environments Collection', async () => {
106108
return e;
107109
});
108110
}
109-
return [envCached1, env1, env2, env3].map((e: PythonEnvLatestInfo) => {
111+
return [envCached1, envCached2, env1, env2, env3].map((e: PythonEnvLatestInfo) => {
110112
e.hasLatestInfo = true;
111113
return e;
112114
});

src/test/pythonEnvironments/common/environmentManagers/conda.unit.test.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -622,21 +622,22 @@ suite('Conda and its environments are located correctly', () => {
622622
test('Must iterate conda environments correctly', async () => {
623623
const locator = new CondaEnvironmentLocator();
624624
const envs = await getEnvs(locator.iterEnvs());
625-
626-
assertBasicEnvsEqual(
627-
envs,
628-
[
629-
'/home/user/miniconda3',
630-
'/home/user/miniconda3/envs/env1',
631-
// no env2, because there's no bin/python* under it
632-
'/home/user/miniconda3/envs/dir/env3',
633-
'/home/user/.conda/envs/env4',
634-
// no env5, because there's no bin/python* under it
635-
'/env6',
636-
].map((envPath) =>
637-
createBasicEnv(PythonEnvKind.Conda, path.join(envPath, 'bin', 'python'), undefined, envPath),
638-
),
625+
const expected = [
626+
'/home/user/miniconda3',
627+
'/home/user/miniconda3/envs/env1',
628+
'/home/user/miniconda3/envs/dir/env3',
629+
'/home/user/.conda/envs/env4',
630+
'/env6',
631+
].map((envPath) =>
632+
createBasicEnv(PythonEnvKind.Conda, path.join(envPath, 'bin', 'python'), undefined, envPath),
633+
);
634+
expected.push(
635+
...[
636+
'/home/user/miniconda3/envs/env2', // Show env2 despite there's no bin/python* under it
637+
'/home/user/.conda/envs/env5', // Show env5 despite there's no bin/python* under it
638+
].map((envPath) => createBasicEnv(PythonEnvKind.Conda, 'python', undefined, envPath)),
639639
);
640+
assertBasicEnvsEqual(envs, expected);
640641
});
641642
});
642643
});

0 commit comments

Comments
 (0)