Skip to content

Commit 7780dcc

Browse files
authored
Add source field to environment info (#15192)
* Add source field to environment info * Test fixes * Change source to enum * Update code to use PythonEnvSource enum instead of raw strings * Change source Path enum to PathEnvVar * Fix tests * Add API to get interpreters by source * Fix missed conda source * Update field comments * Add source to windows path locator * Fix tests
1 parent 2c438e0 commit 7780dcc

21 files changed

Lines changed: 220 additions & 122 deletions

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
import { cloneDeep } from 'lodash';
4+
import { cloneDeep, uniq } from 'lodash';
55
import * as path from 'path';
66
import { getArchitectureDisplayName } from '../../../common/platform/registry';
77
import { normalizeFilename } from '../../../common/utils/filesystem';
@@ -11,7 +11,15 @@ import { getKindDisplayName, getPrioritizedEnvKinds } from './envKind';
1111
import { parseVersionFromExecutable } from './executable';
1212
import { areIdenticalVersion, areSimilarVersions, getVersionDisplayString, isVersionEmpty } from './pythonVersion';
1313

14-
import { FileInfo, PythonDistroInfo, PythonEnvInfo, PythonEnvKind, PythonReleaseLevel, PythonVersion } from '.';
14+
import {
15+
FileInfo,
16+
PythonDistroInfo,
17+
PythonEnvInfo,
18+
PythonEnvKind,
19+
PythonEnvSource,
20+
PythonReleaseLevel,
21+
PythonVersion,
22+
} from '.';
1523

1624
/**
1725
* Create a new info object with all values empty.
@@ -21,14 +29,17 @@ import { FileInfo, PythonDistroInfo, PythonEnvInfo, PythonEnvKind, PythonRelease
2129
export function buildEnvInfo(init?: {
2230
kind?: PythonEnvKind;
2331
executable?: string;
32+
name?: string;
2433
location?: string;
2534
version?: PythonVersion;
2635
org?: string;
2736
arch?: Architecture;
2837
fileInfo?: { ctime: number; mtime: number };
38+
source?: PythonEnvSource[];
39+
defaultDisplayName?: string;
2940
}): PythonEnvInfo {
3041
const env = {
31-
name: '',
42+
name: init?.name ?? '',
3243
location: '',
3344
kind: PythonEnvKind.Unknown,
3445
executable: {
@@ -38,7 +49,7 @@ export function buildEnvInfo(init?: {
3849
mtime: init?.fileInfo?.mtime ?? -1,
3950
},
4051
searchLocation: undefined,
41-
defaultDisplayName: undefined,
52+
defaultDisplayName: init?.defaultDisplayName,
4253
version: {
4354
major: -1,
4455
minor: -1,
@@ -52,6 +63,7 @@ export function buildEnvInfo(init?: {
5263
distro: {
5364
org: init?.org ?? '',
5465
},
66+
source: init?.source ?? [],
5567
};
5668
if (init !== undefined) {
5769
updateEnv(env, init);
@@ -481,6 +493,7 @@ export function mergeEnvironments(target: PythonEnvInfo, other: PythonEnvInfo):
481493
merged.name = merged.name.length ? merged.name : other.name;
482494
merged.searchLocation = merged.searchLocation ?? other.searchLocation;
483495
merged.version = version;
496+
merged.source = uniq([...target.source, ...other.source]);
484497

485498
return merged;
486499
}

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,37 @@ export type PythonExecutableInfo = FileInfo & {
4949
sysPrefix: string;
5050
};
5151

52+
/**
53+
* Source types indicating how a particular environment was discovered.
54+
*
55+
* Notes: This is used in auto-selection to figure out which python to select.
56+
* We added this field to support the existing mechanism in the extension to
57+
* calculate the auto-select python.
58+
*/
59+
export enum PythonEnvSource {
60+
/**
61+
* Environment was found via PATH env variable
62+
*/
63+
PathEnvVar = 'path env var',
64+
/**
65+
* Environment was found via conda binary or conda environments file
66+
*/
67+
Conda = 'conda',
68+
/**
69+
* Environment was found at pyenv default location
70+
*/
71+
Pyenv = 'pyenv',
72+
/**
73+
* Environment was found in windows registry
74+
*/
75+
WindowsRegistry = 'windows registry',
76+
/**
77+
* Environment was found using other means
78+
*/
79+
Other = 'other',
80+
// If source turns out to be useful we will expand this enum to contain more details sources.
81+
}
82+
5283
/**
5384
* The most fundamental information about a Python environment.
5485
*
@@ -61,6 +92,7 @@ export type PythonExecutableInfo = FileInfo & {
6192
* @prop executable - info about the env's Python binary
6293
* @prop name - the env's distro-specific name, if any
6394
* @prop location - the env's location (on disk), if relevant
95+
* @prop source - the locator[s] which found the environment.
6496
*/
6597
export type PythonEnvBaseInfo = {
6698
kind: PythonEnvKind;
@@ -72,6 +104,8 @@ export type PythonEnvBaseInfo = {
72104
// * managed: boolean (if the env is "managed")
73105
// * parent: PythonEnvBaseInfo (the env from which this one was created)
74106
// * binDir: string (where env-installed executables are found)
107+
108+
source: PythonEnvSource[];
75109
};
76110

77111
/**

src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
// tslint:disable-next-line:no-single-line-block-comment
55
/* eslint-disable max-classes-per-file */
66

7+
import { uniq } from 'lodash';
78
import { Event } from 'vscode';
89
import { getSearchPathEntries } from '../../../../common/utils/exec';
910
import { Disposables, IDisposable } from '../../../../common/utils/resourceLifecycle';
1011
import { isStandardPythonBinary } from '../../../common/commonUtils';
11-
import { PythonEnvInfo, PythonEnvKind } from '../../info';
12+
import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../info';
1213
import { ILocator, IPythonEnvsIterator, PythonLocatorQuery } from '../../locator';
1314
import { Locators } from '../../locators';
1415
import { getEnvs } from '../../locatorUtils';
@@ -68,16 +69,28 @@ function getDirFilesLocator(
6869
const envs = await getEnvs(locator.iterEnvs(query));
6970
for (const env of envs) {
7071
if (isStandardPythonBinary(env.executable?.filename || '')) {
72+
env.source = env.source
73+
? uniq([...env.source, PythonEnvSource.PathEnvVar])
74+
: [PythonEnvSource.PathEnvVar];
7175
yield env;
7276
}
7377
}
7478
}
7579
async function resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
7680
const executable = typeof env === 'string' ? env : env.executable?.filename || '';
81+
7782
if (!isStandardPythonBinary(executable)) {
7883
return undefined;
7984
}
80-
return locator.resolveEnv(env);
85+
const resolved = await locator.resolveEnv(env);
86+
if (resolved) {
87+
const source =
88+
typeof env === 'string'
89+
? [PythonEnvSource.PathEnvVar]
90+
: uniq([...env.source, PythonEnvSource.PathEnvVar]);
91+
resolved.source = source;
92+
}
93+
return resolved;
8194
}
8295
return {
8396
iterEnvs,

src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
import { uniq } from 'lodash';
45
import * as path from 'path';
56
import { Uri } from 'vscode';
67
import { traceVerbose } from '../../../../common/logger';
@@ -17,7 +18,7 @@ import {
1718
isVenvEnvironment,
1819
isVirtualenvEnvironment,
1920
} from '../../../discovery/locators/services/virtualEnvironmentIdentifier';
20-
import { PythonEnvInfo, PythonEnvKind } from '../../info';
21+
import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../info';
2122
import { buildEnvInfo } from '../../info/env';
2223
import { IPythonEnvsIterator } from '../../locator';
2324
import { FSWatchingLocator } from './fsWatchingLocator';
@@ -56,11 +57,16 @@ async function getVirtualEnvKind(interpreterPath: string): Promise<PythonEnvKind
5657
return PythonEnvKind.Unknown;
5758
}
5859

59-
async function buildSimpleVirtualEnvInfo(executablePath: string, kind: PythonEnvKind): Promise<PythonEnvInfo> {
60+
async function buildSimpleVirtualEnvInfo(
61+
executablePath: string,
62+
kind: PythonEnvKind,
63+
source?: PythonEnvSource[],
64+
): Promise<PythonEnvInfo> {
6065
const envInfo = buildEnvInfo({
6166
kind,
6267
version: await getPythonVersionFromPath(executablePath),
6368
executable: executablePath,
69+
source: source ?? [PythonEnvSource.Other],
6470
});
6571
const location = getEnvironmentDirFromPath(executablePath);
6672
envInfo.location = location;
@@ -132,12 +138,13 @@ export class WorkspaceVirtualEnvironmentLocator extends FSWatchingLocator {
132138
// eslint-disable-next-line class-methods-use-this
133139
protected async doResolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
134140
const executablePath = typeof env === 'string' ? env : env.executable.filename;
141+
const source = typeof env === 'string' ? [PythonEnvSource.Other] : uniq([PythonEnvSource.Other, ...env.source]);
135142
if (isParentPath(executablePath, this.root) && (await pathExists(executablePath))) {
136143
// We should extract the kind here to avoid doing is*Environment()
137144
// check multiple times. Those checks are file system heavy and
138145
// we can use the kind to determine this anyway.
139146
const kind = await getVirtualEnvKind(executablePath);
140-
return buildSimpleVirtualEnvInfo(executablePath, kind);
147+
return buildSimpleVirtualEnvInfo(executablePath, kind, source);
141148
}
142149
return undefined;
143150
}

src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33
import '../../../../common/extensions';
4-
import { PythonEnvInfo, PythonEnvKind } from '../../../base/info';
4+
import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../../base/info';
55
import { buildEnvInfo } from '../../../base/info/env';
66
import { IPythonEnvsIterator, Locator } from '../../../base/locator';
77
import { getInterpreterPathFromDir } from '../../../common/commonUtils';
@@ -48,6 +48,7 @@ export class CondaEnvironmentLocator extends Locator {
4848
kind: PythonEnvKind.Conda,
4949
org: AnacondaCompanyName,
5050
location: prefix,
51+
source: [PythonEnvSource.Conda],
5152
});
5253
if (name) {
5354
info.name = name;

src/client/pythonEnvironments/discovery/locators/services/customVirtualEnvLocator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as path from 'path';
66
import { traceVerbose } from '../../../../common/logger';
77
import { chain, iterable } from '../../../../common/utils/async';
88
import { getUserHomeDir } from '../../../../common/utils/platform';
9-
import { PythonEnvInfo, PythonEnvKind } from '../../../base/info';
9+
import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../../base/info';
1010
import { buildEnvInfo } from '../../../base/info/env';
1111
import { IPythonEnvsIterator } from '../../../base/locator';
1212
import { FSWatchingLocator } from '../../../base/locators/lowLevel/fsWatchingLocator';
@@ -85,6 +85,7 @@ async function buildSimpleVirtualEnvInfo(executablePath: string, kind: PythonEnv
8585
kind,
8686
version: await getPythonVersionFromPath(executablePath),
8787
executable: executablePath,
88+
source: [PythonEnvSource.Other],
8889
});
8990
const location = getEnvironmentDirFromPath(executablePath);
9091
envInfo.location = location;

src/client/pythonEnvironments/discovery/locators/services/globalVirtualEnvronmentLocator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as path from 'path';
66
import { traceVerbose } from '../../../../common/logger';
77
import { chain, iterable } from '../../../../common/utils/async';
88
import { getEnvironmentVariable, getOSType, getUserHomeDir, OSType } from '../../../../common/utils/platform';
9-
import { PythonEnvInfo, PythonEnvKind } from '../../../base/info';
9+
import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../../base/info';
1010
import { buildEnvInfo } from '../../../base/info/env';
1111
import { IPythonEnvsIterator } from '../../../base/locator';
1212
import { FSWatchingLocator } from '../../../base/locators/lowLevel/fsWatchingLocator';
@@ -84,6 +84,7 @@ async function buildSimpleVirtualEnvInfo(executablePath: string, kind: PythonEnv
8484
kind,
8585
version: await getPythonVersionFromPath(executablePath),
8686
executable: executablePath,
87+
source: [PythonEnvSource.Other],
8788
});
8889
const location = getEnvironmentDirFromPath(executablePath);
8990
envInfo.location = location;

src/client/pythonEnvironments/discovery/locators/services/posixKnownPathsLocator.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import * as path from 'path';
66
import { traceError, traceInfo } from '../../../../common/logger';
77

88
import { Architecture } from '../../../../common/utils/platform';
9-
import { PythonEnvInfo, PythonEnvKind, PythonReleaseLevel, PythonVersion } from '../../../base/info';
9+
import { PythonEnvInfo, PythonEnvKind, PythonEnvSource, PythonReleaseLevel, PythonVersion } from '../../../base/info';
10+
import { buildEnvInfo } from '../../../base/info/env';
1011
import { parseVersion } from '../../../base/info/pythonVersion';
1112
import { IPythonEnvsIterator, Locator } from '../../../base/locator';
1213
import { getFileInfo, resolveSymbolicLink } from '../../../common/externalDependencies';
@@ -40,20 +41,20 @@ export class PosixKnownPathsLocator extends Locator {
4041
private kind: PythonEnvKind = PythonEnvKind.OtherGlobal;
4142

4243
public iterEnvs(): IPythonEnvsIterator {
43-
const buildEnvInfo = (bin: string) => this.buildEnvInfo(bin);
44+
const buildPathEnvInfo = (bin: string) => this.buildPathEnvInfo(bin);
4445
const iterator = async function* () {
4546
const exes = await getPythonBinFromKnownPaths();
46-
yield* exes.map(buildEnvInfo);
47+
yield* exes.map(buildPathEnvInfo);
4748
};
4849
return iterator();
4950
}
5051

5152
public resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
5253
const executablePath = typeof env === 'string' ? env : env.executable.filename;
53-
return this.buildEnvInfo(executablePath);
54+
return this.buildPathEnvInfo(executablePath);
5455
}
5556

56-
private async buildEnvInfo(bin: string): Promise<PythonEnvInfo> {
57+
private async buildPathEnvInfo(bin: string): Promise<PythonEnvInfo> {
5758
let version: PythonVersion;
5859
try {
5960
version = parseVersion(path.basename(bin));
@@ -67,18 +68,15 @@ export class PosixKnownPathsLocator extends Locator {
6768
sysVersion: undefined,
6869
};
6970
}
70-
return {
71+
return buildEnvInfo({
7172
name: '',
7273
location: '',
7374
kind: this.kind,
74-
executable: {
75-
filename: bin,
76-
sysPrefix: '',
77-
...(await getFileInfo(bin)),
78-
},
75+
executable: bin,
76+
fileInfo: await getFileInfo(bin),
7977
version,
8078
arch: Architecture.Unknown,
81-
distro: { org: '' },
82-
};
79+
source: [PythonEnvSource.PathEnvVar],
80+
});
8381
}
8482
}

0 commit comments

Comments
 (0)