Skip to content

Commit 0a8ffd8

Browse files
Kartik Rajericsnowcurrentlykarthiknadig
authored andcommitted
Update reducer to use new implementation of merge environments (microsoft#14152)
* 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 * Undo unintentional formatting changes * And? * Fix missing default * Fix linting Co-authored-by: Eric Snow <[email protected]> Co-authored-by: Karthik Nadig <[email protected]>
1 parent da75ffb commit 0a8ffd8

File tree

4 files changed

+209
-173
lines changed

4 files changed

+209
-173
lines changed

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

Lines changed: 16 additions & 67 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,
@@ -109,7 +111,9 @@ export function getMinimalPartialInfo(env: string | Partial<PythonEnvInfo>): Par
109111
return undefined;
110112
}
111113
return {
112-
executable: { filename: env, sysPrefix: '', ctime: -1, mtime: -1 },
114+
executable: {
115+
filename: env, sysPrefix: '', ctime: -1, mtime: -1,
116+
},
113117
};
114118
}
115119
if (env.executable === undefined) {
@@ -136,7 +140,7 @@ export function getMinimalPartialInfo(env: string | Partial<PythonEnvInfo>): Par
136140
export function areSameEnv(
137141
left: string | Partial<PythonEnvInfo>,
138142
right: string | Partial<PythonEnvInfo>,
139-
allowPartialMatch?: boolean,
143+
allowPartialMatch = true,
140144
): boolean | undefined {
141145
const leftInfo = getMinimalPartialInfo(left);
142146
const rightInfo = getMinimalPartialInfo(right);
@@ -173,7 +177,7 @@ export function areSameEnv(
173177
* weighted by most important to least important fields.
174178
* Wn > Wn-1 + Wn-2 + ... W0
175179
*/
176-
function getPythonVersionInfoHeuristic(version:PythonVersion): number {
180+
function getPythonVersionInfoHeuristic(version: PythonVersion): number {
177181
let infoLevel = 0;
178182
if (version.major > 0) {
179183
infoLevel += 20; // W4
@@ -205,7 +209,7 @@ function getPythonVersionInfoHeuristic(version:PythonVersion): number {
205209
* weighted by most important to least important fields.
206210
* Wn > Wn-1 + Wn-2 + ... W0
207211
*/
208-
function getFileInfoHeuristic(file:FileInfo): number {
212+
function getFileInfoHeuristic(file: FileInfo): number {
209213
let infoLevel = 0;
210214
if (file.filename.length > 0) {
211215
infoLevel += 5; // W2
@@ -229,7 +233,7 @@ function getFileInfoHeuristic(file:FileInfo): number {
229233
* weighted by most important to least important fields.
230234
* Wn > Wn-1 + Wn-2 + ... W0
231235
*/
232-
function getDistroInfoHeuristic(distro:PythonDistroInfo):number {
236+
function getDistroInfoHeuristic(distro: PythonDistroInfo): number {
233237
let infoLevel = 0;
234238
if (distro.org.length > 0) {
235239
infoLevel += 20; // W3
@@ -250,62 +254,6 @@ function getDistroInfoHeuristic(distro:PythonDistroInfo):number {
250254
return infoLevel;
251255
}
252256

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-
309257
/**
310258
* Merges properties of the `target` environment and `other` environment and returns the merged environment.
311259
* if the value in the `target` environment is not defined or has less information. This does not mutate
@@ -318,18 +266,19 @@ export function mergeEnvironments(target: PythonEnvInfo, other: PythonEnvInfo):
318266

319267
const version = cloneDeep(
320268
getPythonVersionInfoHeuristic(target.version) > getPythonVersionInfoHeuristic(other.version)
321-
? target.version : other.version,
269+
? target.version
270+
: other.version,
322271
);
323272

324273
const executable = cloneDeep(
325274
getFileInfoHeuristic(target.executable) > getFileInfoHeuristic(other.executable)
326-
? target.executable : other.executable,
275+
? target.executable
276+
: other.executable,
327277
);
328278
executable.sysPrefix = target.executable.sysPrefix ?? other.executable.sysPrefix;
329279

330280
const distro = cloneDeep(
331-
getDistroInfoHeuristic(target.distro) > getDistroInfoHeuristic(other.distro)
332-
? target.distro : other.distro,
281+
getDistroInfoHeuristic(target.distro) > getDistroInfoHeuristic(other.distro) ? target.distro : other.distro,
333282
);
334283

335284
merged.arch = merged.arch === Architecture.Unknown ? other.arch : target.arch;
@@ -341,8 +290,8 @@ export function mergeEnvironments(target: PythonEnvInfo, other: PythonEnvInfo):
341290
// preferred env based on kind.
342291
merged.kind = target.kind;
343292

344-
merged.location = merged.location ?? other.location;
345-
merged.name = merged.name ?? other.name;
293+
merged.location = merged.location.length ? merged.location : other.location;
294+
merged.name = merged.name.length ? merged.name : other.name;
346295
merged.searchLocation = merged.searchLocation ?? other.searchLocation;
347296
merged.version = version;
348297

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.
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)