Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/1 Enhancements/18934.md
Original file line number Diff line number Diff line change
@@ -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.
18 changes: 12 additions & 6 deletions src/client/pythonEnvironments/base/info/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/client/pythonEnvironments/base/info/environmentInfoService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -100,6 +102,20 @@ class EnvironmentInfoService implements IEnvironmentInfoService {
env: PythonEnvInfo,
priority?: EnvironmentInfoServiceQueuePriority,
): Promise<InterpreterInformation | undefined> {
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<PythonEnvInfo, InterpreterInformation | undefined>(
buildEnvironmentInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ export class PythonEnvInfoCache extends PythonEnvsWatcher<PythonEnvCollectionCha
* we avoid the cost of running lstat. So simply remove envs which no longer
* exist.
*/
const areEnvsValid = await Promise.all(this.envs.map((e) => 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)
Expand Down
18 changes: 3 additions & 15 deletions src/client/pythonEnvironments/common/environmentManagers/conda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
isParentPath,
pathExists,
readFile,
shellExecute,
onDidChangePythonSetting,
exec,
} from '../externalDependencies';
Expand All @@ -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';
Expand Down Expand Up @@ -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<string | undefined> {
try {
const executablePath = await getInterpreterPath(condaEnv.prefix);
Expand All @@ -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<string[] | undefined> {
const condaVersion = await this.getCondaVersion();
if (condaVersion && lt(condaVersion, CONDA_RUN_VERSION)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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'),
Expand All @@ -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;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});