Skip to content

Commit 1a0c790

Browse files
author
Kartik Raj
authored
Added environments resolver (#14019)
* Modify environment info worker to support new environment type * Change worker to return interpreter information instead * Modify resolveEnv() * Code reviews * Code reviews * Move stuff
1 parent 66a4cb1 commit 1a0c790

File tree

5 files changed

+394
-14
lines changed

5 files changed

+394
-14
lines changed

src/client/pythonEnvironments/base/locator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export type PythonLocatorQuery = BasicPythonLocatorQuery & {
9292
searchLocations?: Uri[];
9393
};
9494

95-
export type QueryForEvent<E> = E extends PythonEnvsChangedEvent ? PythonLocatorQuery : BasicPythonLocatorQuery;
95+
type QueryForEvent<E> = E extends PythonEnvsChangedEvent ? PythonLocatorQuery : BasicPythonLocatorQuery;
9696

9797
/**
9898
* A single Python environment locator.

src/client/pythonEnvironments/collection/environmentsReducer.ts renamed to src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33

44
import { cloneDeep, isEqual } from 'lodash';
55
import { Event, EventEmitter } from 'vscode';
6-
import { traceVerbose } from '../../common/logger';
7-
import { createDeferred } from '../../common/utils/async';
8-
import { areSameEnvironment, PythonEnvInfo, PythonEnvKind } from '../base/info';
6+
import { traceVerbose } from '../../../../common/logger';
7+
import { createDeferred } from '../../../../common/utils/async';
8+
import { areSameEnvironment, PythonEnvInfo, PythonEnvKind } from '../../info';
99
import {
10-
ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, QueryForEvent,
11-
} from '../base/locator';
12-
import { PythonEnvsChangedEvent } from '../base/watcher';
10+
ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery,
11+
} from '../../locator';
12+
import { PythonEnvsChangedEvent } from '../../watcher';
1313

1414
/**
1515
* Combines duplicate environments received from the incoming locator into one and passes on unique environments
@@ -47,7 +47,7 @@ export class PythonEnvsReducer implements ILocator {
4747
return this.parentLocator.resolveEnv(environment);
4848
}
4949

50-
public iterEnvs(query?: QueryForEvent<PythonEnvsChangedEvent>): IPythonEnvsIterator {
50+
public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator {
5151
const didUpdate = new EventEmitter<PythonEnvUpdatedEvent | null>();
5252
const incomingIterator = this.parentLocator.iterEnvs(query);
5353
const iterator: IPythonEnvsIterator = iterEnvsIterator(incomingIterator, didUpdate);
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { cloneDeep } from 'lodash';
5+
import { Event, EventEmitter } from 'vscode';
6+
import { traceVerbose } from '../../../../common/logger';
7+
import { IEnvironmentInfoService } from '../../../info/environmentInfoService';
8+
import { areSameEnvironment, PythonEnvInfo } from '../../info';
9+
import { InterpreterInformation } from '../../info/interpreter';
10+
import {
11+
ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, PythonLocatorQuery,
12+
} from '../../locator';
13+
import { PythonEnvsChangedEvent } from '../../watcher';
14+
15+
/**
16+
* Calls environment info service which runs `interpreterInfo.py` script on environments received
17+
* from the parent locator. Uses information received to populate environments further and pass it on.
18+
*/
19+
export class PythonEnvsResolver implements ILocator {
20+
public get onChanged(): Event<PythonEnvsChangedEvent> {
21+
return this.parentLocator.onChanged;
22+
}
23+
24+
constructor(
25+
private readonly parentLocator: ILocator,
26+
private readonly environmentInfoService: IEnvironmentInfoService,
27+
) {}
28+
29+
public async resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
30+
const environment = await this.parentLocator.resolveEnv(env);
31+
if (!environment) {
32+
return undefined;
33+
}
34+
const interpreterInfo = await this.environmentInfoService.getEnvironmentInfo(environment.executable.filename);
35+
if (!interpreterInfo) {
36+
return undefined;
37+
}
38+
return getResolvedEnv(interpreterInfo, environment);
39+
}
40+
41+
public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator {
42+
const didUpdate = new EventEmitter<PythonEnvUpdatedEvent | null>();
43+
const incomingIterator = this.parentLocator.iterEnvs(query);
44+
const iterator: IPythonEnvsIterator = this.iterEnvsIterator(incomingIterator, didUpdate);
45+
iterator.onUpdated = didUpdate.event;
46+
return iterator;
47+
}
48+
49+
private async* iterEnvsIterator(
50+
iterator: IPythonEnvsIterator,
51+
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>,
52+
): AsyncIterator<PythonEnvInfo, void> {
53+
const state = {
54+
done: false,
55+
pending: 0,
56+
};
57+
const seen: PythonEnvInfo[] = [];
58+
59+
if (iterator.onUpdated !== undefined) {
60+
iterator.onUpdated((event) => {
61+
if (event === null) {
62+
state.done = true;
63+
checkIfFinishedAndNotify(state, didUpdate);
64+
} else {
65+
const oldIndex = seen.findIndex((s) => areSameEnvironment(s, event.old));
66+
if (oldIndex !== -1) {
67+
seen[oldIndex] = event.new;
68+
state.pending += 1;
69+
this.resolveInBackground(oldIndex, state, didUpdate, seen).ignoreErrors();
70+
} else {
71+
// This implies a problem in a downstream locator
72+
traceVerbose(`Expected already iterated env in resolver, got ${event.old}`);
73+
}
74+
}
75+
});
76+
}
77+
78+
let result = await iterator.next();
79+
while (!result.done) {
80+
const currEnv = result.value;
81+
seen.push(currEnv);
82+
yield currEnv;
83+
state.pending += 1;
84+
this.resolveInBackground(seen.indexOf(currEnv), state, didUpdate, seen).ignoreErrors();
85+
// eslint-disable-next-line no-await-in-loop
86+
result = await iterator.next();
87+
}
88+
if (iterator.onUpdated === undefined) {
89+
state.done = true;
90+
checkIfFinishedAndNotify(state, didUpdate);
91+
}
92+
}
93+
94+
private async resolveInBackground(
95+
envIndex: number,
96+
state: { done: boolean; pending: number },
97+
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>,
98+
seen: PythonEnvInfo[],
99+
) {
100+
const interpreterInfo = await this.environmentInfoService.getEnvironmentInfo(
101+
seen[envIndex].executable.filename,
102+
);
103+
if (interpreterInfo) {
104+
const resolvedEnv = getResolvedEnv(interpreterInfo, seen[envIndex]);
105+
didUpdate.fire({ old: seen[envIndex], new: resolvedEnv });
106+
seen[envIndex] = resolvedEnv;
107+
}
108+
state.pending -= 1;
109+
checkIfFinishedAndNotify(state, didUpdate);
110+
}
111+
}
112+
113+
/**
114+
* When all info from incoming iterator has been received and all background calls finishes, notify that we're done
115+
* @param state Carries the current state of progress
116+
* @param didUpdate Used to notify when finished
117+
*/
118+
function checkIfFinishedAndNotify(
119+
state: { done: boolean; pending: number },
120+
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>,
121+
) {
122+
if (state.done && state.pending === 0) {
123+
didUpdate.fire(null);
124+
didUpdate.dispose();
125+
}
126+
}
127+
128+
function getResolvedEnv(interpreterInfo: InterpreterInformation, environment: PythonEnvInfo) {
129+
// Deep copy into a new object
130+
const resolvedEnv = cloneDeep(environment);
131+
resolvedEnv.version = interpreterInfo.version;
132+
resolvedEnv.executable.filename = interpreterInfo.executable.filename;
133+
resolvedEnv.executable.sysPrefix = interpreterInfo.executable.sysPrefix;
134+
resolvedEnv.arch = interpreterInfo.arch;
135+
return resolvedEnv;
136+
}

src/test/pythonEnvironments/collection/environmentsReducer.unit.test.ts renamed to src/test/pythonEnvironments/base/locators/composite/environmentsReducer.unit.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import { assert, expect } from 'chai';
55
import { isEqual } from 'lodash';
66
import * as path from 'path';
77
import { EventEmitter } from 'vscode';
8-
import { PythonEnvInfo, PythonEnvKind } from '../../../client/pythonEnvironments/base/info';
9-
import { PythonEnvUpdatedEvent } from '../../../client/pythonEnvironments/base/locator';
10-
import { PythonEnvsChangedEvent } from '../../../client/pythonEnvironments/base/watcher';
8+
import { PythonEnvInfo, PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info';
9+
import { PythonEnvUpdatedEvent } from '../../../../../client/pythonEnvironments/base/locator';
1110
import {
1211
mergeEnvironments,
1312
PythonEnvsReducer,
14-
} from '../../../client/pythonEnvironments/collection/environmentsReducer';
15-
import { sleep } from '../../core';
16-
import { createEnv, getEnvs, SimpleLocator } from '../base/common';
13+
} from '../../../../../client/pythonEnvironments/base/locators/composite/environmentsReducer';
14+
import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments/base/watcher';
15+
import { sleep } from '../../../../core';
16+
import { createEnv, getEnvs, SimpleLocator } from '../../common';
1717

1818
suite('Environments Reducer', () => {
1919
suite('iterEnvs()', () => {

0 commit comments

Comments
 (0)