Skip to content

Commit 98c0387

Browse files
ericsnowcurrentlyKartik Raj
authored and
Kartik Raj
committed
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 218dd8d commit 98c0387

File tree

4 files changed

+219
-185
lines changed

4 files changed

+219
-185
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: 69 additions & 55 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,28 +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-
// eslint-disable-next-line no-await-in-loop
42-
result = await iterator.next();
43-
}
26+
const environments = await getEnvs(this.iterEnvs());
27+
const environment = environments.find((e) => areSameEnv(e, env));
4428
if (!environment) {
4529
return undefined;
4630
}
47-
await waitForUpdatesDeferred.promise;
4831
return this.parentLocator.resolveEnv(environment);
4932
}
5033

@@ -72,15 +55,12 @@ async function* iterEnvsIterator(
7255
if (event === null) {
7356
state.done = true;
7457
checkIfFinishedAndNotify(state, didUpdate);
58+
} else if (seen[event.index] !== undefined) {
59+
state.pending += 1;
60+
resolveDifferencesInBackground(event.index, event.update, state, didUpdate, seen).ignoreErrors();
7561
} else {
76-
if (seen[event.index] !== undefined) {
77-
state.pending += 1;
78-
resolveDifferencesInBackground(event.index, event.update, state, didUpdate, seen)
79-
.ignoreErrors();
80-
} else {
81-
// This implies a problem in a downstream locator
82-
traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`);
83-
}
62+
// This implies a problem in a downstream locator
63+
traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`);
8464
}
8565
});
8666
}
@@ -114,7 +94,7 @@ async function resolveDifferencesInBackground(
11494
seen: PythonEnvInfo[],
11595
) {
11696
const oldEnv = seen[oldIndex];
117-
const merged = mergeEnvironments(oldEnv, newEnv);
97+
const merged = resolveEnvCollision(oldEnv, newEnv);
11898
if (!isEqual(oldEnv, merged)) {
11999
seen[oldIndex] = merged;
120100
didUpdate.fire({ index: oldIndex, old: oldEnv, update: merged });
@@ -138,29 +118,63 @@ function checkIfFinishedAndNotify(
138118
}
139119
}
140120

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

0 commit comments

Comments
 (0)