diff --git a/news/1 Enhancements/18934.md b/news/1 Enhancements/18934.md new file mode 100644 index 000000000000..dff7142e2947 --- /dev/null +++ b/news/1 Enhancements/18934.md @@ -0,0 +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. diff --git a/src/client/pythonEnvironments/base/info/env.ts b/src/client/pythonEnvironments/base/info/env.ts index 68de19af6d42..a9aea9d42a97 100644 --- a/src/client/pythonEnvironments/base/info/env.ts +++ b/src/client/pythonEnvironments/base/info/env.ts @@ -251,12 +251,18 @@ export function areSameEnv( return true; } - if (allowPartialMatch && arePathsSame(path.dirname(leftFilename), path.dirname(rightFilename))) { - const leftVersion = typeof left === 'string' ? undefined : leftInfo.version; - const rightVersion = typeof right === 'string' ? undefined : rightInfo.version; - if (leftVersion && rightVersion) { - if (areIdenticalVersion(leftVersion, rightVersion) || areSimilarVersions(leftVersion, rightVersion)) { - return true; + if (allowPartialMatch) { + const isSameDirectory = + leftFilename !== 'python' && + rightFilename !== 'python' && + arePathsSame(path.dirname(leftFilename), path.dirname(rightFilename)); + if (isSameDirectory) { + const leftVersion = typeof left === 'string' ? undefined : leftInfo.version; + const rightVersion = typeof right === 'string' ? undefined : rightInfo.version; + if (leftVersion && rightVersion) { + if (areIdenticalVersion(leftVersion, rightVersion) || areSimilarVersions(leftVersion, rightVersion)) { + return true; + } } } } diff --git a/src/client/pythonEnvironments/base/info/environmentInfoService.ts b/src/client/pythonEnvironments/base/info/environmentInfoService.ts index baf1eb873bc2..c6d4308bee89 100644 --- a/src/client/pythonEnvironments/base/info/environmentInfoService.ts +++ b/src/client/pythonEnvironments/base/info/environmentInfoService.ts @@ -11,6 +11,8 @@ import { Conda, CONDA_ACTIVATION_TIMEOUT, isCondaEnvironment } from '../../commo import { PythonEnvInfo, PythonEnvKind } from '.'; import { normCasePath } from '../../common/externalDependencies'; import { OUTPUT_MARKER_SCRIPT } from '../../../common/process/internal/scripts'; +import { Architecture } from '../../../common/utils/platform'; +import { getEmptyVersion } from './pythonVersion'; export enum EnvironmentInfoServiceQueuePriority { Default, @@ -100,6 +102,20 @@ class EnvironmentInfoService implements IEnvironmentInfoService { env: PythonEnvInfo, priority?: EnvironmentInfoServiceQueuePriority, ): Promise { + if (env.kind === PythonEnvKind.Conda && env.executable.filename === 'python') { + const emptyInterpreterInfo: InterpreterInformation = { + arch: Architecture.Unknown, + executable: { + filename: 'python', + ctime: -1, + mtime: -1, + sysPrefix: '', + }, + version: getEmptyVersion(), + }; + + return emptyInterpreterInfo; + } if (this.workerPool === undefined) { this.workerPool = createRunningWorkerPool( buildEnvironmentInfo, diff --git a/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts b/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts index 396382ca3558..7c12faf524c4 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts @@ -86,7 +86,9 @@ export class PythonEnvInfoCache extends PythonEnvsWatcher pathExists(e.executable.filename))); + const areEnvsValid = await Promise.all( + this.envs.map((e) => (e.executable.filename === 'python' ? true : pathExists(e.executable.filename))), + ); const invalidIndexes = areEnvsValid .map((isValid, index) => (isValid ? -1 : index)) .filter((i) => i !== -1) diff --git a/src/client/pythonEnvironments/common/environmentManagers/conda.ts b/src/client/pythonEnvironments/common/environmentManagers/conda.ts index cd708b91a988..0f862a7bc0b6 100644 --- a/src/client/pythonEnvironments/common/environmentManagers/conda.ts +++ b/src/client/pythonEnvironments/common/environmentManagers/conda.ts @@ -8,7 +8,6 @@ import { isParentPath, pathExists, readFile, - shellExecute, onDidChangePythonSetting, exec, } from '../externalDependencies'; @@ -22,8 +21,6 @@ import { cache } from '../../../common/utils/decorators'; import { isTestExecution } from '../../../common/constants'; import { traceError, traceVerbose } from '../../../logging'; import { OUTPUT_MARKER_SCRIPT } from '../../../common/process/internal/scripts'; -import { buildPythonExecInfo } from '../../exec'; -import { getExecutablePath } from '../../info/executable'; export const AnacondaCompanyName = 'Anaconda, Inc.'; export const CONDAPATH_SETTING_KEY = 'condaPath'; @@ -483,6 +480,7 @@ export class Conda { /** * Returns executable associated with the conda env, swallows exceptions. */ + // eslint-disable-next-line class-methods-use-this public async getInterpreterPathForEnvironment(condaEnv: CondaEnvInfo): Promise { try { const executablePath = await getInterpreterPath(condaEnv.prefix); @@ -491,26 +489,16 @@ export class Conda { return executablePath; } traceVerbose( - 'Executable does not exist within conda env, running conda run to get it', + 'Executable does not exist within conda env, assume the executable to be `python`', JSON.stringify(condaEnv), ); - return this.getInterpreterPathUsingCondaRun(condaEnv); + return 'python'; } catch (ex) { traceError(`Failed to get executable for conda env: ${JSON.stringify(condaEnv)}`, ex); return undefined; } } - @cache(-1, true) - private async getInterpreterPathUsingCondaRun(condaEnv: CondaEnvInfo) { - const runArgs = await this.getRunPythonArgs(condaEnv); - if (runArgs) { - const python = buildPythonExecInfo(runArgs); - return getExecutablePath(python, shellExecute, CONDA_ACTIVATION_TIMEOUT); - } - return undefined; - } - public async getRunPythonArgs(env: CondaEnvInfo, forShellExecution?: boolean): Promise { const condaVersion = await this.getCondaVersion(); if (condaVersion && lt(condaVersion, CONDA_RUN_VERSION)) { diff --git a/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts index 81648e7aeb35..1dd31c4424e5 100644 --- a/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/envsCollectionService.unit.test.ts @@ -78,7 +78,8 @@ suite('Python envs locator - Environments Collection', async () => { path.join(TEST_LAYOUT_ROOT, 'pipenv', 'project1', '.venv', 'Scripts', 'python.exe'), Uri.file(TEST_LAYOUT_ROOT), ); - return [envCached1, envCached2]; + const envCached3 = createEnv('python'); + return [envCached1, envCached2, envCached3]; } function getCachedEnvs() { @@ -89,6 +90,7 @@ suite('Python envs locator - Environments Collection', async () => { function getExpectedEnvs(doNotIncludeCached?: boolean) { const fakeLocalAppDataPath = path.join(TEST_LAYOUT_ROOT, 'storeApps'); const envCached1 = createEnv(path.join(fakeLocalAppDataPath, 'Microsoft', 'WindowsApps', 'python.exe')); + const envCached2 = createEnv('python'); const env1 = createEnv(path.join(TEST_LAYOUT_ROOT, 'conda1', 'python.exe'), undefined, updatedName); const env2 = createEnv( path.join(TEST_LAYOUT_ROOT, 'pipenv', 'project1', '.venv', 'Scripts', 'python.exe'), @@ -106,7 +108,7 @@ suite('Python envs locator - Environments Collection', async () => { return e; }); } - return [envCached1, env1, env2, env3].map((e: PythonEnvLatestInfo) => { + return [envCached1, envCached2, env1, env2, env3].map((e: PythonEnvLatestInfo) => { e.hasLatestInfo = true; return e; }); diff --git a/src/test/pythonEnvironments/common/environmentManagers/conda.unit.test.ts b/src/test/pythonEnvironments/common/environmentManagers/conda.unit.test.ts index 1764db2cb5d0..845fb7dee88c 100644 --- a/src/test/pythonEnvironments/common/environmentManagers/conda.unit.test.ts +++ b/src/test/pythonEnvironments/common/environmentManagers/conda.unit.test.ts @@ -622,21 +622,22 @@ suite('Conda and its environments are located correctly', () => { test('Must iterate conda environments correctly', async () => { const locator = new CondaEnvironmentLocator(); const envs = await getEnvs(locator.iterEnvs()); - - assertBasicEnvsEqual( - envs, - [ - '/home/user/miniconda3', - '/home/user/miniconda3/envs/env1', - // no env2, because there's no bin/python* under it - '/home/user/miniconda3/envs/dir/env3', - '/home/user/.conda/envs/env4', - // no env5, because there's no bin/python* under it - '/env6', - ].map((envPath) => - createBasicEnv(PythonEnvKind.Conda, path.join(envPath, 'bin', 'python'), undefined, envPath), - ), + const expected = [ + '/home/user/miniconda3', + '/home/user/miniconda3/envs/env1', + '/home/user/miniconda3/envs/dir/env3', + '/home/user/.conda/envs/env4', + '/env6', + ].map((envPath) => + createBasicEnv(PythonEnvKind.Conda, path.join(envPath, 'bin', 'python'), undefined, envPath), + ); + expected.push( + ...[ + '/home/user/miniconda3/envs/env2', // Show env2 despite there's no bin/python* under it + '/home/user/.conda/envs/env5', // Show env5 despite there's no bin/python* under it + ].map((envPath) => createBasicEnv(PythonEnvKind.Conda, 'python', undefined, envPath)), ); + assertBasicEnvsEqual(envs, expected); }); }); });