Skip to content

Commit

Permalink
Resolve conda environments without relying on the updated list of con…
Browse files Browse the repository at this point in the history
…da envs (#20112)

Closes #20069
Closes #20110
Closes #20070
  • Loading branch information
Kartik Raj authored Oct 28, 2022
1 parent 1db65b8 commit 43c059e
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 140 deletions.
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

0 comments on commit 43c059e

Please sign in to comment.