Skip to content

Commit deeb5e3

Browse files
ericsnowcurrentlykarthiknadig
authored andcommitted
Add PythonEnvInfo-related helpers. (microsoft#14051)
This PR adds some basic helpers that we use in a subsequent PR. The following small drive-by changes are also included: * drop PythonEnvInfo.id property * make some internal helpers public
1 parent 0a1bbbd commit deeb5e3

File tree

4 files changed

+217
-180
lines changed

4 files changed

+217
-180
lines changed

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

Lines changed: 26 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export function buildEnvInfo(init?: {
3737
},
3838
name: '',
3939
location: '',
40+
searchLocation: undefined,
41+
defaultDisplayName: undefined,
4042
version: {
4143
major: -1,
4244
minor: -1,
@@ -65,7 +67,7 @@ export function buildEnvInfo(init?: {
6567
export function copyEnvInfo(
6668
env: PythonEnvInfo,
6769
updates?: {
68-
kind?: PythonEnvKind,
70+
kind?: PythonEnvKind;
6971
},
7072
): PythonEnvInfo {
7173
// We don't care whether or not extra/hidden properties
@@ -77,12 +79,15 @@ export function copyEnvInfo(
7779
return copied;
7880
}
7981

80-
function updateEnv(env: PythonEnvInfo, updates: {
81-
kind?: PythonEnvKind;
82-
executable?: string;
83-
location?: string;
84-
version?: PythonVersion;
85-
}): void {
82+
function updateEnv(
83+
env: PythonEnvInfo,
84+
updates: {
85+
kind?: PythonEnvKind;
86+
executable?: string;
87+
location?: string;
88+
version?: PythonVersion;
89+
},
90+
): void {
8691
if (updates.kind !== undefined) {
8792
env.kind = updates.kind;
8893
}
@@ -109,7 +114,9 @@ export function getMinimalPartialInfo(env: string | Partial<PythonEnvInfo>): Par
109114
return undefined;
110115
}
111116
return {
112-
executable: { filename: env, sysPrefix: '', ctime: -1, mtime: -1 },
117+
executable: {
118+
filename: env, sysPrefix: '', ctime: -1, mtime: -1,
119+
},
113120
};
114121
}
115122
if (env.executable === undefined) {
@@ -136,7 +143,7 @@ export function getMinimalPartialInfo(env: string | Partial<PythonEnvInfo>): Par
136143
export function areSameEnv(
137144
left: string | Partial<PythonEnvInfo>,
138145
right: string | Partial<PythonEnvInfo>,
139-
allowPartialMatch?: boolean,
146+
allowPartialMatch = true,
140147
): boolean | undefined {
141148
const leftInfo = getMinimalPartialInfo(left);
142149
const rightInfo = getMinimalPartialInfo(right);
@@ -173,7 +180,7 @@ export function areSameEnv(
173180
* weighted by most important to least important fields.
174181
* Wn > Wn-1 + Wn-2 + ... W0
175182
*/
176-
function getPythonVersionInfoHeuristic(version:PythonVersion): number {
183+
function getPythonVersionInfoHeuristic(version: PythonVersion): number {
177184
let infoLevel = 0;
178185
if (version.major > 0) {
179186
infoLevel += 20; // W4
@@ -205,7 +212,7 @@ function getPythonVersionInfoHeuristic(version:PythonVersion): number {
205212
* weighted by most important to least important fields.
206213
* Wn > Wn-1 + Wn-2 + ... W0
207214
*/
208-
function getFileInfoHeuristic(file:FileInfo): number {
215+
function getFileInfoHeuristic(file: FileInfo): number {
209216
let infoLevel = 0;
210217
if (file.filename.length > 0) {
211218
infoLevel += 5; // W2
@@ -229,7 +236,7 @@ function getFileInfoHeuristic(file:FileInfo): number {
229236
* weighted by most important to least important fields.
230237
* Wn > Wn-1 + Wn-2 + ... W0
231238
*/
232-
function getDistroInfoHeuristic(distro:PythonDistroInfo):number {
239+
function getDistroInfoHeuristic(distro: PythonDistroInfo): number {
233240
let infoLevel = 0;
234241
if (distro.org.length > 0) {
235242
infoLevel += 20; // W3
@@ -250,62 +257,6 @@ function getDistroInfoHeuristic(distro:PythonDistroInfo):number {
250257
return infoLevel;
251258
}
252259

253-
/**
254-
* Gets a prioritized list of environment types for identification.
255-
* @returns {PythonEnvKind[]} : List of environments ordered by identification priority
256-
*
257-
* Remarks: This is the order of detection based on how the various distributions and tools
258-
* configure the environment, and the fall back for identification.
259-
* Top level we have the following environment types, since they leave a unique signature
260-
* in the environment or * use a unique path for the environments they create.
261-
* 1. Conda
262-
* 2. Windows Store
263-
* 3. PipEnv
264-
* 4. Pyenv
265-
* 5. Poetry
266-
*
267-
* Next level we have the following virtual environment tools. The are here because they
268-
* are consumed by the tools above, and can also be used independently.
269-
* 1. venv
270-
* 2. virtualenvwrapper
271-
* 3. virtualenv
272-
*
273-
* Last category is globally installed python, or system python.
274-
*/
275-
export function getPrioritizedEnvironmentKind(): PythonEnvKind[] {
276-
return [
277-
PythonEnvKind.CondaBase,
278-
PythonEnvKind.Conda,
279-
PythonEnvKind.WindowsStore,
280-
PythonEnvKind.Pipenv,
281-
PythonEnvKind.Pyenv,
282-
PythonEnvKind.Poetry,
283-
PythonEnvKind.Venv,
284-
PythonEnvKind.VirtualEnvWrapper,
285-
PythonEnvKind.VirtualEnv,
286-
PythonEnvKind.OtherVirtual,
287-
PythonEnvKind.OtherGlobal,
288-
PythonEnvKind.MacDefault,
289-
PythonEnvKind.System,
290-
PythonEnvKind.Custom,
291-
PythonEnvKind.Unknown,
292-
];
293-
}
294-
295-
/**
296-
* Selects an environment based on the environment selection priority. This should
297-
* match the priority in the environment identifier.
298-
*/
299-
export function sortEnvInfoByPriority(...envs: PythonEnvInfo[]): PythonEnvInfo[] {
300-
// tslint:disable-next-line: no-suspicious-comment
301-
// TODO: When we consolidate the PythonEnvKind and EnvironmentType we should have
302-
// one location where we define priority and
303-
const envKindByPriority:PythonEnvKind[] = getPrioritizedEnvironmentKind();
304-
return envs.sort(
305-
(a:PythonEnvInfo, b:PythonEnvInfo) => envKindByPriority.indexOf(a.kind) - envKindByPriority.indexOf(b.kind),
306-
);
307-
}
308-
309260
/**
310261
* Merges properties of the `target` environment and `other` environment and returns the merged environment.
311262
* if the value in the `target` environment is not defined or has less information. This does not mutate
@@ -318,18 +269,19 @@ export function mergeEnvironments(target: PythonEnvInfo, other: PythonEnvInfo):
318269

319270
const version = cloneDeep(
320271
getPythonVersionInfoHeuristic(target.version) > getPythonVersionInfoHeuristic(other.version)
321-
? target.version : other.version,
272+
? target.version
273+
: other.version,
322274
);
323275

324276
const executable = cloneDeep(
325277
getFileInfoHeuristic(target.executable) > getFileInfoHeuristic(other.executable)
326-
? target.executable : other.executable,
278+
? target.executable
279+
: other.executable,
327280
);
328281
executable.sysPrefix = target.executable.sysPrefix ?? other.executable.sysPrefix;
329282

330283
const distro = cloneDeep(
331-
getDistroInfoHeuristic(target.distro) > getDistroInfoHeuristic(other.distro)
332-
? target.distro : other.distro,
284+
getDistroInfoHeuristic(target.distro) > getDistroInfoHeuristic(other.distro) ? target.distro : other.distro,
333285
);
334286

335287
merged.arch = merged.arch === Architecture.Unknown ? other.arch : target.arch;
@@ -341,8 +293,8 @@ export function mergeEnvironments(target: PythonEnvInfo, other: PythonEnvInfo):
341293
// preferred env based on kind.
342294
merged.kind = target.kind;
343295

344-
merged.location = merged.location ?? other.location;
345-
merged.name = merged.name ?? other.name;
296+
merged.location = merged.location.length ? merged.location : other.location;
297+
merged.name = merged.name.length ? merged.name : other.name;
346298
merged.searchLocation = merged.searchLocation ?? other.searchLocation;
347299
merged.version = version;
348300

src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts

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

4-
import { cloneDeep, isEqual } from 'lodash';
4+
import { isEqual } from 'lodash';
55
import { Event, EventEmitter } from 'vscode';
66
import { traceVerbose } from '../../../../common/logger';
7-
import { createDeferred } from '../../../../common/utils/async';
87
import { PythonEnvInfo, PythonEnvKind } from '../../info';
9-
import { areSameEnv } from '../../info/env';
8+
import { areSameEnv, mergeEnvironments } from '../../info/env';
109
import {
1110
ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery,
1211
} from '../../locator';
12+
import { getEnvs } from '../../locatorUtils';
1313
import { PythonEnvsChangedEvent } from '../../watcher';
1414

1515
/**
@@ -23,27 +23,11 @@ export class PythonEnvsReducer implements ILocator {
2323
constructor(private readonly parentLocator: ILocator) {}
2424

2525
public async resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
26-
let environment: PythonEnvInfo | undefined;
27-
const waitForUpdatesDeferred = createDeferred<void>();
28-
const iterator = this.iterEnvs();
29-
iterator.onUpdated!((event) => {
30-
if (event === null) {
31-
waitForUpdatesDeferred.resolve();
32-
} else if (environment && areSameEnv(environment, event.update)) {
33-
environment = event.update;
34-
}
35-
});
36-
let result = await iterator.next();
37-
while (!result.done) {
38-
if (areSameEnv(result.value, env)) {
39-
environment = result.value;
40-
}
41-
result = await iterator.next();
42-
}
26+
const environments = await getEnvs(this.iterEnvs());
27+
const environment = environments.find((e) => areSameEnv(e, env));
4328
if (!environment) {
4429
return undefined;
4530
}
46-
await waitForUpdatesDeferred.promise;
4731
return this.parentLocator.resolveEnv(environment);
4832
}
4933

@@ -73,8 +57,7 @@ async function* iterEnvsIterator(
7357
checkIfFinishedAndNotify(state, didUpdate);
7458
} else if (seen[event.index] !== undefined) {
7559
state.pending += 1;
76-
resolveDifferencesInBackground(event.index, event.update, state, didUpdate, seen)
77-
.ignoreErrors();
60+
resolveDifferencesInBackground(event.index, event.update, state, didUpdate, seen).ignoreErrors();
7861
} else {
7962
// This implies a problem in a downstream locator
8063
traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`);
@@ -110,7 +93,7 @@ async function resolveDifferencesInBackground(
11093
seen: PythonEnvInfo[],
11194
) {
11295
const oldEnv = seen[oldIndex];
113-
const merged = mergeEnvironments(oldEnv, newEnv);
96+
const merged = resolveEnvCollision(oldEnv, newEnv);
11497
if (!isEqual(oldEnv, merged)) {
11598
seen[oldIndex] = merged;
11699
didUpdate.fire({ index: oldIndex, old: oldEnv, update: merged });
@@ -134,29 +117,63 @@ function checkIfFinishedAndNotify(
134117
}
135118
}
136119

137-
export function mergeEnvironments(environment: PythonEnvInfo, other: PythonEnvInfo): PythonEnvInfo {
138-
const result = cloneDeep(environment);
139-
// Preserve type information.
140-
// Possible we identified environment as unknown, but a later provider has identified env type.
141-
if (environment.kind === PythonEnvKind.Unknown && other.kind && other.kind !== PythonEnvKind.Unknown) {
142-
result.kind = other.kind;
143-
}
144-
const props: (keyof PythonEnvInfo)[] = [
145-
'version',
146-
'kind',
147-
'executable',
148-
'name',
149-
'arch',
150-
'distro',
151-
'defaultDisplayName',
152-
'searchLocation',
120+
function resolveEnvCollision(oldEnv: PythonEnvInfo, newEnv: PythonEnvInfo): PythonEnvInfo {
121+
const [env, other] = sortEnvInfoByPriority(oldEnv, newEnv);
122+
return mergeEnvironments(env, other);
123+
}
124+
125+
/**
126+
* Selects an environment based on the environment selection priority. This should
127+
* match the priority in the environment identifier.
128+
*/
129+
function sortEnvInfoByPriority(...envs: PythonEnvInfo[]): PythonEnvInfo[] {
130+
// tslint:disable-next-line: no-suspicious-comment
131+
// TODO: When we consolidate the PythonEnvKind and EnvironmentType we should have
132+
// one location where we define priority and
133+
const envKindByPriority: PythonEnvKind[] = getPrioritizedEnvironmentKind();
134+
return envs.sort(
135+
(a: PythonEnvInfo, b: PythonEnvInfo) => envKindByPriority.indexOf(a.kind) - envKindByPriority.indexOf(b.kind),
136+
);
137+
}
138+
139+
/**
140+
* Gets a prioritized list of environment types for identification.
141+
* @returns {PythonEnvKind[]} : List of environments ordered by identification priority
142+
*
143+
* Remarks: This is the order of detection based on how the various distributions and tools
144+
* configure the environment, and the fall back for identification.
145+
* Top level we have the following environment types, since they leave a unique signature
146+
* in the environment or * use a unique path for the environments they create.
147+
* 1. Conda
148+
* 2. Windows Store
149+
* 3. PipEnv
150+
* 4. Pyenv
151+
* 5. Poetry
152+
*
153+
* Next level we have the following virtual environment tools. The are here because they
154+
* are consumed by the tools above, and can also be used independently.
155+
* 1. venv
156+
* 2. virtualenvwrapper
157+
* 3. virtualenv
158+
*
159+
* Last category is globally installed python, or system python.
160+
*/
161+
function getPrioritizedEnvironmentKind(): PythonEnvKind[] {
162+
return [
163+
PythonEnvKind.CondaBase,
164+
PythonEnvKind.Conda,
165+
PythonEnvKind.WindowsStore,
166+
PythonEnvKind.Pipenv,
167+
PythonEnvKind.Pyenv,
168+
PythonEnvKind.Poetry,
169+
PythonEnvKind.Venv,
170+
PythonEnvKind.VirtualEnvWrapper,
171+
PythonEnvKind.VirtualEnv,
172+
PythonEnvKind.OtherVirtual,
173+
PythonEnvKind.OtherGlobal,
174+
PythonEnvKind.MacDefault,
175+
PythonEnvKind.System,
176+
PythonEnvKind.Custom,
177+
PythonEnvKind.Unknown,
153178
];
154-
props.forEach((prop) => {
155-
if (!result[prop] && other[prop]) {
156-
// tslint:disable: no-any
157-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
158-
(result as any)[prop] = other[prop];
159-
}
160-
});
161-
return result;
162179
}

0 commit comments

Comments
 (0)