Skip to content

Resolve conda environments without relying on the updated list of conda envs #20112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Oct 28, 2022
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
19 changes: 8 additions & 11 deletions src/client/proposedApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,19 +271,19 @@ export function convertCompleteEnvInfo(env: PythonEnvInfo): ResolvedEnvironment
path,
id: getEnvID(path),
executable: {
uri: Uri.file(env.executable.filename),
uri: env.executable.filename === 'python' ? undefined : Uri.file(env.executable.filename),
bitness: convertBitness(env.arch),
sysPrefix: env.executable.sysPrefix,
},
environment: env.type
? {
type: convertEnvType(env.type),
name: env.name,
name: env.name === '' ? undefined : env.name,
folderUri: Uri.file(env.location),
workspaceFolder: env.searchLocation,
}
: undefined,
version: version as ResolvedEnvironment['version'],
version: env.executable.filename === 'python' ? undefined : (version as ResolvedEnvironment['version']),
tools: tool ? [tool] : [],
};
return resolvedEnv;
Expand Down Expand Up @@ -325,19 +325,16 @@ export function convertEnvInfo(env: PythonEnvInfo): Environment {
if (convertedEnv.executable.sysPrefix === '') {
convertedEnv.executable.sysPrefix = undefined;
}
if (convertedEnv.executable.uri?.fsPath === 'python') {
convertedEnv.executable.uri = undefined;
if (convertedEnv.version?.sysVersion === '') {
convertedEnv.version.sysVersion = undefined;
}
if (convertedEnv.environment?.name === '') {
convertedEnv.environment.name = undefined;
}
if (convertedEnv.version.major === -1) {
if (convertedEnv.version?.major === -1) {
convertedEnv.version.major = undefined;
}
if (convertedEnv.version.micro === -1) {
if (convertedEnv.version?.micro === -1) {
convertedEnv.version.micro = undefined;
}
if (convertedEnv.version.minor === -1) {
if (convertedEnv.version?.minor === -1) {
convertedEnv.version.minor = undefined;
}
return convertedEnv as Environment;
Expand Down
32 changes: 18 additions & 14 deletions src/client/proposedApiTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,16 @@ export type Environment = EnvironmentPath & {
}
| undefined;
/**
* Carries Python version information known at this moment.
* Carries Python version information known at this moment, carries `undefined` for envs without python.
*/
readonly version: VersionInfo & {
/**
* Value of `sys.version` in sys module if known at this moment.
*/
readonly sysVersion: string | undefined;
};
readonly version:
| (VersionInfo & {
/**
* Value of `sys.version` in sys module if known at this moment.
*/
readonly sysVersion: string | undefined;
})
| undefined;
/**
* Tools/plugins which created the environment or where it came from. First value in array corresponds
* to the primary tool which manages the environment, which never changes over time.
Expand Down Expand Up @@ -171,14 +173,16 @@ export type ResolvedEnvironment = Environment & {
readonly sysPrefix: string;
};
/**
* Carries complete Python version information.
* Carries complete Python version information, carries `undefined` for envs without python.
*/
readonly version: ResolvedVersionInfo & {
/**
* Value of `sys.version` in sys module if known at this moment.
*/
readonly sysVersion: string;
};
readonly version:
| (ResolvedVersionInfo & {
/**
* Value of `sys.version` in sys module if known at this moment.
*/
readonly sysVersion: string;
})
| undefined;
};

export type EnvironmentsChangeEvent = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class PythonEnvsResolver implements IResolvingLocator {
} else if (seen[event.index] !== undefined) {
const old = seen[event.index];
await setKind(event.update, environmentKinds);
seen[event.index] = await resolveBasicEnv(event.update, true);
seen[event.index] = await resolveBasicEnv(event.update);
didUpdate.fire({ old, index: event.index, update: seen[event.index] });
this.resolveInBackground(event.index, state, didUpdate, seen).ignoreErrors();
} else {
Expand All @@ -113,7 +113,7 @@ export class PythonEnvsResolver implements IResolvingLocator {
while (!result.done) {
// Use cache from the current refresh where possible.
await setKind(result.value, environmentKinds);
const currEnv = await resolveBasicEnv(result.value, true);
const currEnv = await resolveBasicEnv(result.value);
seen.push(currEnv);
yield currEnv;
this.resolveInBackground(seen.indexOf(currEnv), state, didUpdate, seen).ignoreErrors();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,8 @@ import {
UNKNOWN_PYTHON_VERSION,
virtualEnvKinds,
} from '../../info';
import {
buildEnvInfo,
comparePythonVersionSpecificity,
setEnvDisplayString,
areSameEnv,
getEnvID,
} from '../../info/env';
import {
getEnvironmentDirFromPath,
getInterpreterPathFromDir,
getPythonVersionFromPath,
} from '../../../common/commonUtils';
import { buildEnvInfo, comparePythonVersionSpecificity, setEnvDisplayString, getEnvID } from '../../info/env';
import { getEnvironmentDirFromPath, getPythonVersionFromPath } from '../../../common/commonUtils';
import { arePathsSame, getFileInfo, isParentPath } from '../../../common/externalDependencies';
import { AnacondaCompanyName, Conda, isCondaEnvironment } from '../../../common/environmentManagers/conda';
import { getPyenvVersionsDir, parsePyenvVersion } from '../../../common/environmentManagers/pyenv';
Expand All @@ -36,8 +26,8 @@ import { traceError, traceWarn } from '../../../../logging';
import { isVirtualEnvironment } from '../../../common/environmentManagers/simplevirtualenvs';
import { getWorkspaceFolderPaths } from '../../../../common/vscodeApis/workspaceApis';

function getResolvers(): Map<PythonEnvKind, (env: BasicEnvInfo, useCache?: boolean) => Promise<PythonEnvInfo>> {
const resolvers = new Map<PythonEnvKind, (_: BasicEnvInfo, useCache?: boolean) => Promise<PythonEnvInfo>>();
function getResolvers(): Map<PythonEnvKind, (env: BasicEnvInfo) => Promise<PythonEnvInfo>> {
const resolvers = new Map<PythonEnvKind, (_: BasicEnvInfo) => Promise<PythonEnvInfo>>();
Object.values(PythonEnvKind).forEach((k) => {
resolvers.set(k, resolveGloballyInstalledEnv);
});
Expand All @@ -55,11 +45,11 @@ function getResolvers(): Map<PythonEnvKind, (env: BasicEnvInfo, useCache?: boole
* executable and returns it. Notice `undefined` is never returned, so environment
* returned could still be invalid.
*/
export async function resolveBasicEnv(env: BasicEnvInfo, useCache = false): Promise<PythonEnvInfo> {
export async function resolveBasicEnv(env: BasicEnvInfo): Promise<PythonEnvInfo> {
const { kind, source } = env;
const resolvers = getResolvers();
const resolverForKind = resolvers.get(kind)!;
const resolvedEnv = await resolverForKind(env, useCache);
const resolvedEnv = await resolverForKind(env);
resolvedEnv.searchLocation = getSearchLocation(resolvedEnv);
resolvedEnv.source = uniq(resolvedEnv.source.concat(source ?? []));
if (getOSType() === OSType.Windows && resolvedEnv.source?.includes(PythonEnvSource.WindowsRegistry)) {
Expand Down Expand Up @@ -165,47 +155,41 @@ async function resolveSimpleEnv(env: BasicEnvInfo): Promise<PythonEnvInfo> {
return envInfo;
}

async function resolveCondaEnv(env: BasicEnvInfo, useCache?: boolean): Promise<PythonEnvInfo> {
async function resolveCondaEnv(env: BasicEnvInfo): Promise<PythonEnvInfo> {
const { executablePath } = env;
const conda = await Conda.getConda();
if (conda === undefined) {
traceWarn(`${executablePath} identified as Conda environment even though Conda is not installed`);
traceWarn(`${executablePath} identified as Conda environment even though Conda is not found`);
// Environment could still be valid, resolve as a simple env.
env.kind = PythonEnvKind.Unknown;
const envInfo = await resolveSimpleEnv(env);
envInfo.type = PythonEnvType.Conda;
// Assume it's a prefixed env by default because prefixed CLIs work even for named environments.
envInfo.name = '';
return envInfo;
}
const envs = (await conda?.getEnvList(useCache)) ?? [];
for (const { name, prefix } of envs) {
let executable = await getInterpreterPathFromDir(prefix);
const currEnv: BasicEnvInfo = { executablePath: executable ?? '', kind: PythonEnvKind.Conda, envPath: prefix };
if (areSameEnv(env, currEnv)) {
if (env.executablePath.length > 0) {
executable = env.executablePath;
} else {
executable = await conda?.getInterpreterPathForEnvironment({ name, prefix });
}
const info = buildEnvInfo({
executable,
kind: PythonEnvKind.Conda,
org: AnacondaCompanyName,
location: prefix,
source: [],
version: executable ? await getPythonVersionFromPath(executable) : undefined,
type: PythonEnvType.Conda,
});
if (name) {
info.name = name;
}
return info;
}

const envPath = env.envPath ?? getEnvironmentDirFromPath(env.executablePath);
let executable: string;
if (env.executablePath.length > 0) {
executable = env.executablePath;
} else {
executable = await conda.getInterpreterPathForEnvironment({ prefix: envPath });
}
traceError(
`${env.envPath ?? env.executablePath} identified as a Conda environment but is not returned via '${
conda?.command
} info' command`,
);
// Environment could still be valid, resolve as a simple env.
env.kind = PythonEnvKind.Unknown;
const envInfo = await resolveSimpleEnv(env);
envInfo.type = PythonEnvType.Conda;
return envInfo;
const info = buildEnvInfo({
executable,
kind: PythonEnvKind.Conda,
org: AnacondaCompanyName,
location: envPath,
source: [],
version: executable ? await getPythonVersionFromPath(executable) : undefined,
type: PythonEnvType.Conda,
});
const name = await conda?.getName(envPath);
if (name) {
info.name = name;
}
return info;
}

async function resolvePyenvEnv(env: BasicEnvInfo): Promise<PythonEnvInfo> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,8 @@ export class CondaEnvironmentLocator extends FSWatchingLocator {
try {
traceVerbose(`Looking into conda env for executable: ${JSON.stringify(env)}`);
const executablePath = await conda.getInterpreterPathForEnvironment(env);
if (executablePath !== undefined) {
traceVerbose(`Found conda executable: ${executablePath}`);
yield { kind: PythonEnvKind.Conda, executablePath, envPath: env.prefix };
} else {
traceError(`Executable for conda env not found: ${JSON.stringify(env)}`);
}
traceVerbose(`Found conda executable: ${executablePath}`);
yield { kind: PythonEnvKind.Conda, executablePath, envPath: env.prefix };
} catch (ex) {
traceError(`Failed to process conda env: ${JSON.stringify(env)}`, ex);
}
Expand Down
65 changes: 30 additions & 35 deletions src/client/pythonEnvironments/common/environmentManagers/conda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,34 +444,34 @@ export class Conda {
* Corresponds to "conda env list --json", but also computes environment names.
*/
@cache(30_000, true, 10_000)
public async getEnvList(useCache?: boolean): Promise<CondaEnvInfo[]> {
const info = await this.getInfo(useCache);
public async getEnvList(): Promise<CondaEnvInfo[]> {
const info = await this.getInfo();
const { envs } = info;
if (envs === undefined) {
return [];
}
return Promise.all(
envs.map(async (prefix) => ({
prefix,
name: await this.getName(prefix, info),
})),
);
}

function getName(prefix: string) {
if (info.root_prefix && arePathsSame(prefix, info.root_prefix)) {
return 'base';
}

const parentDir = path.dirname(prefix);
if (info.envs_dirs !== undefined) {
for (const envsDir of info.envs_dirs) {
if (arePathsSame(parentDir, envsDir)) {
return path.basename(prefix);
}
public async getName(prefix: string, info?: CondaInfo): Promise<string | undefined> {
info = info ?? (await this.getInfo(true));
if (info.root_prefix && arePathsSame(prefix, info.root_prefix)) {
return 'base';
}
const parentDir = path.dirname(prefix);
if (info.envs_dirs !== undefined) {
for (const envsDir of info.envs_dirs) {
if (arePathsSame(parentDir, envsDir)) {
return path.basename(prefix);
}
}

return undefined;
}

return envs.map((prefix) => ({
prefix,
name: getName(prefix),
}));
return undefined;
}

/**
Expand All @@ -493,22 +493,17 @@ 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);
if (executablePath) {
traceVerbose('Found executable within conda env', JSON.stringify(condaEnv));
return executablePath;
}
traceVerbose(
'Executable does not exist within conda env, assume the executable to be `python`',
JSON.stringify(condaEnv),
);
return 'python';
} catch (ex) {
traceError(`Failed to get executable for conda env: ${JSON.stringify(condaEnv)}`, ex);
return undefined;
public async getInterpreterPathForEnvironment(condaEnv: CondaEnvInfo | { prefix: string }): Promise<string> {
const executablePath = await getInterpreterPath(condaEnv.prefix);
if (executablePath) {
traceVerbose('Found executable within conda env', JSON.stringify(condaEnv));
return executablePath;
}
traceVerbose(
'Executable does not exist within conda env, assume the executable to be `python`',
JSON.stringify(condaEnv),
);
return 'python';
}

public async getRunPythonArgs(env: CondaEnvInfo, forShellExecution?: boolean): Promise<string[] | undefined> {
Expand Down
Loading