Skip to content

Commit 707b39a

Browse files
author
Kartik Raj
committed
Added environments resolver
1 parent df2d801 commit 707b39a

File tree

3 files changed

+147
-1
lines changed

3 files changed

+147
-1
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import { Uri } from 'vscode';
55
import { Architecture } from '../../../common/utils/platform';
66
import { BasicVersionInfo, VersionInfo } from '../../../common/utils/version';
7+
import { arePathsSame } from '../../common/externalDependencies';
78

89
/**
910
* IDs for the various supported Python environments.
@@ -143,3 +144,22 @@ export type PythonEnvInfo = _PythonEnvInfo & {
143144
defaultDisplayName?: string;
144145
searchLocation?: Uri;
145146
};
147+
148+
/**
149+
* Determine if the given infos correspond to the same env.
150+
*
151+
* @param environment1 - one of the two envs to compare
152+
* @param environment2 - one of the two envs to compare
153+
*/
154+
export function areSameEnvironment(
155+
environment1: PythonEnvInfo,
156+
environment2: PythonEnvInfo,
157+
): boolean {
158+
if (!environment1 || !environment2) {
159+
return false;
160+
}
161+
if (arePathsSame(environment1.executable.filename, environment2.executable.filename)) {
162+
return true;
163+
}
164+
return false;
165+
}

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-
type QueryForEvent<E> = E extends PythonEnvsChangedEvent ? PythonLocatorQuery : BasicPythonLocatorQuery;
95+
export type QueryForEvent<E> = E extends PythonEnvsChangedEvent ? PythonLocatorQuery : BasicPythonLocatorQuery;
9696

9797
/**
9898
* A single Python environment locator.
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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 { areSameEnvironment, PythonEnvInfo } from '../base/info';
8+
import { InterpreterInformation } from '../base/info/interpreter';
9+
import {
10+
ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, QueryForEvent,
11+
} from '../base/locator';
12+
import { PythonEnvsChangedEvent } from '../base/watcher';
13+
import { IEnvironmentInfoService } from '../info/environmentInfoService';
14+
15+
export class PythonEnvsReducer implements ILocator {
16+
public get onChanged(): Event<PythonEnvsChangedEvent> {
17+
return this.pythonEnvsReducer.onChanged;
18+
}
19+
20+
constructor(
21+
private readonly pythonEnvsReducer: ILocator,
22+
private readonly environmentInfoService: IEnvironmentInfoService,
23+
) {}
24+
25+
public resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
26+
return this.pythonEnvsReducer.resolveEnv(env);
27+
}
28+
29+
public iterEnvs(query?: QueryForEvent<PythonEnvsChangedEvent>): IPythonEnvsIterator {
30+
const didUpdate = new EventEmitter<PythonEnvUpdatedEvent | null>();
31+
const incomingIterator = this.pythonEnvsReducer.iterEnvs(query);
32+
const iterator: IPythonEnvsIterator = this.iterEnvsIterator(incomingIterator, didUpdate);
33+
iterator.onUpdated = didUpdate.event;
34+
return iterator;
35+
}
36+
37+
private async* iterEnvsIterator(
38+
iterator: IPythonEnvsIterator,
39+
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>,
40+
): AsyncIterator<PythonEnvInfo, void> {
41+
const state = {
42+
done: false,
43+
pending: 0,
44+
};
45+
const seen: PythonEnvInfo[] = [];
46+
47+
if (iterator.onUpdated !== undefined) {
48+
iterator.onUpdated((event) => {
49+
if (event === null) {
50+
state.done = true;
51+
checkIfFinishedAndNotify(state, didUpdate);
52+
} else {
53+
const old = seen.find((s) => areSameEnvironment(s, event.old));
54+
if (old !== undefined) {
55+
didUpdate.fire({ old, new: event.new });
56+
seen[seen.indexOf(old)] = event.new;
57+
state.pending += 1;
58+
this.resolveInBackground(event.new, state, didUpdate, seen).ignoreErrors();
59+
} else {
60+
// This implies a problem in a downstream locator
61+
traceVerbose(`Expected already iterated env in resolver, got ${event.old}`);
62+
}
63+
}
64+
});
65+
}
66+
67+
let result = await iterator.next();
68+
while (!result.done) {
69+
const currEnv = result.value;
70+
// Resolver only expects unique environments, so store & yield as-is.
71+
seen.push(currEnv);
72+
yield currEnv;
73+
state.pending += 1;
74+
this.resolveInBackground(currEnv, state, didUpdate, seen).ignoreErrors();
75+
// eslint-disable-next-line no-await-in-loop
76+
result = await iterator.next();
77+
}
78+
if (iterator.onUpdated === undefined) {
79+
state.done = true;
80+
checkIfFinishedAndNotify(state, didUpdate);
81+
}
82+
}
83+
84+
private async resolveInBackground(
85+
env: PythonEnvInfo,
86+
state: { done: boolean; pending: number },
87+
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>,
88+
seen: PythonEnvInfo[],
89+
) {
90+
const interpreterInfo = await this.environmentInfoService.getEnvironmentInfo(env.executable.filename);
91+
// Old environment to be fired might have changed until now, search for it again
92+
const seenEnv = seen.find((s) => areSameEnvironment(s, env));
93+
if (interpreterInfo && seenEnv) {
94+
const resolvedEnv = getResolvedEnv(interpreterInfo, seenEnv);
95+
didUpdate.fire({ old: seenEnv, new: resolvedEnv });
96+
seen[seen.indexOf(seenEnv)] = resolvedEnv;
97+
}
98+
state.pending -= 1;
99+
checkIfFinishedAndNotify(state, didUpdate);
100+
}
101+
}
102+
103+
/**
104+
* When all info from incoming iterator has been received and all background calls finishes, notify that we're done
105+
* @param state Carries the current state of progress
106+
* @param didUpdate Used to notify when finished
107+
*/
108+
function checkIfFinishedAndNotify(
109+
state: { done: boolean; pending: number },
110+
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>,
111+
) {
112+
if (state.done && state.pending === 0) {
113+
didUpdate.fire(null);
114+
didUpdate.dispose();
115+
}
116+
}
117+
118+
function getResolvedEnv(interpreterInfo: InterpreterInformation, environment: PythonEnvInfo) {
119+
// Deep copy into a new object
120+
const resolvedEnv = cloneDeep(environment);
121+
resolvedEnv.version = interpreterInfo.version;
122+
resolvedEnv.executable.filename = interpreterInfo.executable.filename;
123+
resolvedEnv.executable.sysPrefix = interpreterInfo.executable.sysPrefix;
124+
resolvedEnv.arch = interpreterInfo.arch;
125+
return resolvedEnv;
126+
}

0 commit comments

Comments
 (0)