Skip to content

Commit b922c16

Browse files
author
Kartik Raj
committed
Allow low level locators to emit a single kind
1 parent 7d469e3 commit b922c16

File tree

10 files changed

+109
-82
lines changed

10 files changed

+109
-82
lines changed

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ import {
2020
PythonVersion,
2121
virtualEnvKinds,
2222
} from '.';
23-
import { BasicEnvInfo } from '../locator';
23+
import { CompositeEnvInfo, convertKindIntoArray } from '../locator';
2424

2525
/**
2626
* Create a new info object with all values empty.
2727
*
2828
* @param init - if provided, these values are applied to the new object
2929
*/
3030
export function buildEnvInfo(init?: {
31-
kind?: PythonEnvKind[];
31+
kind?: PythonEnvKind[] | PythonEnvKind;
3232
executable?: string;
3333
name?: string;
3434
location?: string;
@@ -83,7 +83,7 @@ export function buildEnvInfo(init?: {
8383
export function copyEnvInfo(
8484
env: PythonEnvInfo,
8585
updates?: {
86-
kind?: PythonEnvKind[];
86+
kind?: PythonEnvKind[] | PythonEnvKind;
8787
},
8888
): PythonEnvInfo {
8989
// We don't care whether or not extra/hidden properties
@@ -98,15 +98,15 @@ export function copyEnvInfo(
9898
function updateEnv(
9999
env: PythonEnvInfo,
100100
updates: {
101-
kind?: PythonEnvKind[];
101+
kind?: PythonEnvKind[] | PythonEnvKind;
102102
executable?: string;
103103
location?: string;
104104
version?: PythonVersion;
105105
searchLocation?: Uri;
106106
},
107107
): void {
108108
if (updates.kind !== undefined) {
109-
env.kind = updates.kind;
109+
env.kind = convertKindIntoArray(updates.kind);
110110
}
111111
if (updates.executable !== undefined) {
112112
env.executable.filename = updates.executable;
@@ -173,7 +173,7 @@ function buildEnvDisplayString(env: PythonEnvInfo, getAllDetails = false): strin
173173
* If insufficient data is provided to generate a minimal object, such
174174
* that it is not identifiable, then `undefined` is returned.
175175
*/
176-
function getMinimalPartialInfo(env: string | PythonEnvInfo | BasicEnvInfo): Partial<PythonEnvInfo> | undefined {
176+
function getMinimalPartialInfo(env: string | PythonEnvInfo | CompositeEnvInfo): Partial<PythonEnvInfo> | undefined {
177177
if (typeof env === 'string') {
178178
if (env === '') {
179179
return undefined;
@@ -235,8 +235,8 @@ export function getEnvID(interpreterPath: string, envFolderPath?: string): strin
235235
* where multiple versions of python executables are all put in the same directory.
236236
*/
237237
export function areSameEnv(
238-
left: string | PythonEnvInfo | BasicEnvInfo,
239-
right: string | PythonEnvInfo | BasicEnvInfo,
238+
left: string | PythonEnvInfo | CompositeEnvInfo,
239+
right: string | PythonEnvInfo | CompositeEnvInfo,
240240
allowPartialMatch = true,
241241
): boolean | undefined {
242242
const leftInfo = getMinimalPartialInfo(left);

src/client/pythonEnvironments/base/locator.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,14 +296,31 @@ export type PythonLocatorQuery = BasicPythonLocatorQuery & {
296296

297297
type QueryForEvent<E> = E extends PythonEnvsChangedEvent ? PythonLocatorQuery : BasicPythonLocatorQuery;
298298

299-
export type BasicEnvInfo = {
300-
kind: PythonEnvKind[];
299+
export type BasicEnvInfo<T = PythonEnvKind[] | PythonEnvKind> = {
300+
kind: T;
301301
executablePath: string;
302302
source?: PythonEnvSource[];
303303
envPath?: string;
304304
extensionId?: ExtensionID;
305305
};
306306

307+
/**
308+
* A version of `BasicEnvInfo` used for composite locators.
309+
*/
310+
export type CompositeEnvInfo = BasicEnvInfo<PythonEnvKind[]>;
311+
312+
export function convertBasicToComposite(env: BasicEnvInfo): CompositeEnvInfo {
313+
env.kind = convertKindIntoArray(env.kind);
314+
return env as CompositeEnvInfo;
315+
}
316+
317+
export function convertKindIntoArray(kind: PythonEnvKind | PythonEnvKind[]): PythonEnvKind[] {
318+
if (!Array.isArray(kind)) {
319+
kind = [kind];
320+
}
321+
return kind;
322+
}
323+
307324
/**
308325
* A single Python environment locator.
309326
*

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

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { areSameEnv } from '../../info/env';
99
import { sortExtensionSource, sortKindFunction } from '../../info/envKind';
1010
import {
1111
BasicEnvInfo,
12+
CompositeEnvInfo,
13+
convertBasicToComposite,
1214
ILocator,
1315
IPythonEnvsIterator,
1416
isProgressEvent,
@@ -22,7 +24,7 @@ import { PythonEnvsChangedEvent } from '../../watcher';
2224
/**
2325
* Combines duplicate environments received from the incoming locator into one and passes on unique environments
2426
*/
25-
export class PythonEnvsReducer implements ILocator<BasicEnvInfo> {
27+
export class PythonEnvsReducer implements ILocator<CompositeEnvInfo> {
2628
public get onChanged(): Event<PythonEnvsChangedEvent> {
2729
return this.parentLocator.onChanged;
2830
}
@@ -31,8 +33,8 @@ export class PythonEnvsReducer implements ILocator<BasicEnvInfo> {
3133

3234
constructor(private readonly parentLocator: ILocator<BasicEnvInfo>) {}
3335

34-
public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator<BasicEnvInfo> {
35-
const didUpdate = new EventEmitter<PythonEnvUpdatedEvent<BasicEnvInfo> | ProgressNotificationEvent>();
36+
public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator<CompositeEnvInfo> {
37+
const didUpdate = new EventEmitter<PythonEnvUpdatedEvent<CompositeEnvInfo> | ProgressNotificationEvent>();
3638
const incomingIterator = this.parentLocator.iterEnvs(query);
3739
const iterator = iterEnvsIterator(incomingIterator, didUpdate);
3840
iterator.onUpdated = didUpdate.event;
@@ -42,13 +44,13 @@ export class PythonEnvsReducer implements ILocator<BasicEnvInfo> {
4244

4345
async function* iterEnvsIterator(
4446
iterator: IPythonEnvsIterator<BasicEnvInfo>,
45-
didUpdate: EventEmitter<PythonEnvUpdatedEvent<BasicEnvInfo> | ProgressNotificationEvent>,
46-
): IPythonEnvsIterator<BasicEnvInfo> {
47+
didUpdate: EventEmitter<PythonEnvUpdatedEvent<CompositeEnvInfo> | ProgressNotificationEvent>,
48+
): IPythonEnvsIterator<CompositeEnvInfo> {
4749
const state = {
4850
done: false,
4951
pending: 0,
5052
};
51-
const seen: BasicEnvInfo[] = [];
53+
const seen: CompositeEnvInfo[] = [];
5254

5355
if (iterator.onUpdated !== undefined) {
5456
const listener = iterator.onUpdated((event) => {
@@ -66,8 +68,8 @@ async function* iterEnvsIterator(
6668
);
6769
} else if (seen[event.index] !== undefined) {
6870
const oldEnv = seen[event.index];
69-
seen[event.index] = event.update;
70-
didUpdate.fire({ index: event.index, old: oldEnv, update: event.update });
71+
seen[event.index] = convertBasicToComposite(event.update);
72+
didUpdate.fire({ index: event.index, old: oldEnv, update: seen[event.index] });
7173
} else {
7274
// This implies a problem in a downstream locator
7375
traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`);
@@ -81,7 +83,7 @@ async function* iterEnvsIterator(
8183

8284
let result = await iterator.next();
8385
while (!result.done) {
84-
const currEnv = result.value;
86+
const currEnv = convertBasicToComposite(result.value);
8587
const oldIndex = seen.findIndex((s) => areSameEnv(s, currEnv));
8688
if (oldIndex !== -1) {
8789
resolveDifferencesInBackground(oldIndex, currEnv, state, didUpdate, seen).ignoreErrors();
@@ -100,10 +102,10 @@ async function* iterEnvsIterator(
100102

101103
async function resolveDifferencesInBackground(
102104
oldIndex: number,
103-
newEnv: BasicEnvInfo,
105+
newEnv: CompositeEnvInfo,
104106
state: { done: boolean; pending: number },
105-
didUpdate: EventEmitter<PythonEnvUpdatedEvent<BasicEnvInfo> | ProgressNotificationEvent>,
106-
seen: BasicEnvInfo[],
107+
didUpdate: EventEmitter<PythonEnvUpdatedEvent<CompositeEnvInfo> | ProgressNotificationEvent>,
108+
seen: CompositeEnvInfo[],
107109
) {
108110
state.pending += 1;
109111
// It's essential we increment the pending call count before any asynchronus calls in this method.
@@ -125,15 +127,15 @@ async function resolveDifferencesInBackground(
125127
*/
126128
function checkIfFinishedAndNotify(
127129
state: { done: boolean; pending: number },
128-
didUpdate: EventEmitter<PythonEnvUpdatedEvent<BasicEnvInfo> | ProgressNotificationEvent>,
130+
didUpdate: EventEmitter<PythonEnvUpdatedEvent<CompositeEnvInfo> | ProgressNotificationEvent>,
129131
) {
130132
if (state.done && state.pending === 0) {
131133
didUpdate.fire({ stage: ProgressReportStage.discoveryFinished });
132134
didUpdate.dispose();
133135
}
134136
}
135137

136-
function resolveEnvCollision(oldEnv: BasicEnvInfo, newEnv: BasicEnvInfo): BasicEnvInfo {
138+
function resolveEnvCollision(oldEnv: CompositeEnvInfo, newEnv: CompositeEnvInfo): CompositeEnvInfo {
137139
const [env] = sortEnvInfoByPriority(oldEnv, newEnv);
138140
const merged = cloneDeep(env);
139141
merged.source = union(oldEnv.source ?? [], newEnv.source ?? []);
@@ -145,8 +147,8 @@ function resolveEnvCollision(oldEnv: BasicEnvInfo, newEnv: BasicEnvInfo): BasicE
145147
* Selects an environment based on the environment selection priority. This should
146148
* match the priority in the environment identifier.
147149
*/
148-
function sortEnvInfoByPriority(...envs: BasicEnvInfo[]): BasicEnvInfo[] {
149-
return envs.sort((a: BasicEnvInfo, b: BasicEnvInfo) => {
150+
function sortEnvInfoByPriority(...envs: CompositeEnvInfo[]): CompositeEnvInfo[] {
151+
return envs.sort((a: CompositeEnvInfo, b: CompositeEnvInfo) => {
150152
const kindDiff = sortKindFunction(getTopKind(a.kind), getTopKind(b.kind));
151153
if (kindDiff !== 0) {
152154
return kindDiff;

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { PythonEnvInfo, PythonEnvKind } from '../../info';
99
import { getEnvPath, setEnvDisplayString } from '../../info/env';
1010
import { InterpreterInformation } from '../../info/interpreter';
1111
import {
12-
BasicEnvInfo,
12+
CompositeEnvInfo,
1313
IInternalEnvironmentProvider,
1414
ILocator,
1515
InternalEnvironmentProviderMetadata,
@@ -22,7 +22,7 @@ import {
2222
PythonLocatorQuery,
2323
} from '../../locator';
2424
import { PythonEnvsChangedEvent } from '../../watcher';
25-
import { registerResolver, resolveBasicEnv } from './resolverUtils';
25+
import { registerResolver, resolveCompositeEnv } from './resolverUtils';
2626
import { traceVerbose, traceWarn } from '../../../../logging';
2727
import { getEnvironmentDirFromPath, getInterpreterPathFromDir, isPythonExecutable } from '../../../common/commonUtils';
2828
import { getEmptyVersion } from '../../info/pythonVersion';
@@ -58,15 +58,15 @@ export class PythonEnvsResolver implements IResolvingLocator {
5858
}
5959

6060
constructor(
61-
private readonly parentLocator: ILocator<BasicEnvInfo>,
61+
private readonly parentLocator: ILocator<CompositeEnvInfo>,
6262
private readonly environmentInfoService: IEnvironmentInfoService,
6363
) {}
6464

6565
public async resolveEnv(path: string): Promise<PythonEnvInfo | undefined> {
6666
const [executablePath, envPath] = await getExecutablePathAndEnvPath(path);
6767
path = executablePath.length ? executablePath : envPath;
6868
const kind = await identifyEnvironment(path);
69-
const environment = await resolveBasicEnv({ kind: [kind], executablePath, envPath });
69+
const environment = await resolveCompositeEnv({ kind: [kind], executablePath, envPath });
7070
if (!environment) {
7171
return undefined;
7272
}
@@ -86,7 +86,7 @@ export class PythonEnvsResolver implements IResolvingLocator {
8686
}
8787

8888
private async *iterEnvsIterator(
89-
iterator: IPythonEnvsIterator<BasicEnvInfo>,
89+
iterator: IPythonEnvsIterator<CompositeEnvInfo>,
9090
didUpdate: EventEmitter<PythonEnvUpdatedEvent | ProgressNotificationEvent>,
9191
): IPythonEnvsIterator {
9292
const state = {
@@ -112,7 +112,7 @@ export class PythonEnvsResolver implements IResolvingLocator {
112112
);
113113
} else if (seen[event.index] !== undefined) {
114114
const old = seen[event.index];
115-
const env = await resolveBasicEnv(event.update, true);
115+
const env = await resolveCompositeEnv(event.update, true);
116116
didUpdate.fire({ old, index: event.index, update: env });
117117
if (env) {
118118
seen[event.index] = env;
@@ -132,7 +132,7 @@ export class PythonEnvsResolver implements IResolvingLocator {
132132
let result = await iterator.next();
133133
while (!result.done) {
134134
// Use cache from the current refresh where possible.
135-
const currEnv = await resolveBasicEnv(result.value, true);
135+
const currEnv = await resolveCompositeEnv(result.value, true);
136136
if (currEnv) {
137137
seen.push(currEnv);
138138
yield currEnv;

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

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ import { getPyenvVersionsDir, parsePyenvVersion } from '../../../common/environm
2323
import { Architecture, getOSType, OSType } from '../../../../common/utils/platform';
2424
import { getPythonVersionFromPath as parsePythonVersionFromPath, parseVersion } from '../../info/pythonVersion';
2525
import { getRegistryInterpreters, getRegistryInterpretersSync } from '../../../common/windowsUtils';
26-
import { BasicEnvInfo } from '../../locator';
26+
import { CompositeEnvInfo } from '../../locator';
2727
import { parseVersionFromExecutable } from '../../info/executable';
2828
import { traceError, traceWarn } from '../../../../logging';
2929
import { sortExtensionSource } from '../../info/envKind';
3030

31-
type ResolverType = (_: BasicEnvInfo, useCache?: boolean) => Promise<PythonEnvInfo | undefined>;
31+
type ResolverType = (_: CompositeEnvInfo, useCache?: boolean) => Promise<PythonEnvInfo | undefined>;
3232
type ResolversType = { resolver: ResolverType; extensionId?: string }[];
3333
const resolvers = new Map<PythonEnvKind, ResolverType | ResolversType>();
3434
Object.values(PythonEnvKind).forEach((k) => {
@@ -43,7 +43,7 @@ resolvers.set(PythonEnvKind.Pyenv, resolvePyenvEnv);
4343

4444
export function registerResolver(
4545
kind: PythonEnvKind,
46-
resolver: (_: BasicEnvInfo, useCache?: boolean) => Promise<PythonEnvInfo | undefined>,
46+
resolver: (_: CompositeEnvInfo, useCache?: boolean) => Promise<PythonEnvInfo | undefined>,
4747
extensionId: string,
4848
): void {
4949
const resolversForKind = resolvers.get(kind);
@@ -59,16 +59,19 @@ export function registerResolver(
5959
}
6060

6161
/**
62-
* Find as much info about the given Basic Python env as possible without running the
62+
* Find as much info about the given Python env as possible without running the
6363
* executable and returns it. Notice `undefined` is never returned, so environment
6464
* returned could still be invalid.
6565
*/
66-
export async function resolveBasicEnv(env: BasicEnvInfo, useCache = false): Promise<PythonEnvInfo | undefined> {
66+
export async function resolveCompositeEnv(env: CompositeEnvInfo, useCache = false): Promise<PythonEnvInfo | undefined> {
6767
const { kind, source } = env;
68-
const value = resolvers.get(kind[0]);
68+
let value = resolvers.get(kind[0]);
6969
if (!value) {
70-
traceError('No resolver found for kind:', kind[0]);
71-
return undefined;
70+
value = env.extensionId ? resolvers.get(env.extensionId as PythonEnvKind) : undefined;
71+
if (!value) {
72+
traceError('No resolver found for env:', JSON.stringify(env));
73+
return undefined;
74+
}
7275
}
7376
let resolverForKind: ResolverType;
7477
if (Array.isArray(value)) {
@@ -146,7 +149,7 @@ async function updateEnvUsingRegistry(env: PythonEnvInfo): Promise<void> {
146149
}
147150
}
148151

149-
async function resolveGloballyInstalledEnv(env: BasicEnvInfo): Promise<PythonEnvInfo> {
152+
async function resolveGloballyInstalledEnv(env: CompositeEnvInfo): Promise<PythonEnvInfo> {
150153
const { executablePath } = env;
151154
let version;
152155
try {
@@ -162,7 +165,7 @@ async function resolveGloballyInstalledEnv(env: BasicEnvInfo): Promise<PythonEnv
162165
return envInfo;
163166
}
164167

165-
async function resolveSimpleEnv(env: BasicEnvInfo): Promise<PythonEnvInfo> {
168+
async function resolveSimpleEnv(env: CompositeEnvInfo): Promise<PythonEnvInfo> {
166169
const { executablePath, kind } = env;
167170
const envInfo = buildEnvInfo({
168171
kind,
@@ -175,7 +178,7 @@ async function resolveSimpleEnv(env: BasicEnvInfo): Promise<PythonEnvInfo> {
175178
return envInfo;
176179
}
177180

178-
async function resolveCondaEnv(env: BasicEnvInfo, useCache?: boolean): Promise<PythonEnvInfo> {
181+
async function resolveCondaEnv(env: CompositeEnvInfo, useCache?: boolean): Promise<PythonEnvInfo> {
179182
const { executablePath } = env;
180183
const conda = await Conda.getConda();
181184
if (conda === undefined) {
@@ -184,7 +187,7 @@ async function resolveCondaEnv(env: BasicEnvInfo, useCache?: boolean): Promise<P
184187
const envs = (await conda?.getEnvList(useCache)) ?? [];
185188
for (const { name, prefix } of envs) {
186189
let executable = await getInterpreterPathFromDir(prefix);
187-
const currEnv: BasicEnvInfo = { executablePath: executable ?? '', kind: env.kind, envPath: prefix };
190+
const currEnv: CompositeEnvInfo = { executablePath: executable ?? '', kind: env.kind, envPath: prefix };
188191
if (areSameEnv(env, currEnv)) {
189192
if (env.executablePath.length > 0) {
190193
executable = env.executablePath;
@@ -215,7 +218,7 @@ async function resolveCondaEnv(env: BasicEnvInfo, useCache?: boolean): Promise<P
215218
return resolveSimpleEnv(env);
216219
}
217220

218-
async function resolvePyenvEnv(env: BasicEnvInfo): Promise<PythonEnvInfo> {
221+
async function resolvePyenvEnv(env: CompositeEnvInfo): Promise<PythonEnvInfo> {
219222
const { executablePath } = env;
220223
const location = getEnvironmentDirFromPath(executablePath);
221224
const name = path.basename(location);
@@ -268,7 +271,7 @@ async function isBaseCondaPyenvEnvironment(executablePath: string) {
268271
return arePathsSame(path.dirname(location), pyenvVersionDir);
269272
}
270273

271-
async function resolveWindowsStoreEnv(env: BasicEnvInfo): Promise<PythonEnvInfo> {
274+
async function resolveWindowsStoreEnv(env: CompositeEnvInfo): Promise<PythonEnvInfo> {
272275
const { executablePath } = env;
273276
return buildEnvInfo({
274277
kind: env.kind,

src/client/pythonEnvironments/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ async function createLocator(
106106
// This is shared.
107107
): Promise<IDiscoveryAPI> {
108108
// Create the low-level locators.
109-
let locators: ILocator<BasicEnvInfo> = new ExtensionLocators(
109+
const locators: ILocator<BasicEnvInfo> = new ExtensionLocators(
110110
// Here we pull the locators together.
111111
createNonWorkspaceLocators(ext),
112112
createWorkspaceLocator(ext),
@@ -116,9 +116,9 @@ async function createLocator(
116116
const envInfoService = getEnvironmentInfoService(ext.disposables);
117117

118118
// Build the stack of composite locators.
119-
locators = new PythonEnvsReducer(locators);
119+
const reducer = new PythonEnvsReducer(locators);
120120
const resolvingLocator = new PythonEnvsResolver(
121-
locators,
121+
reducer,
122122
// These are shared.
123123
envInfoService,
124124
);

0 commit comments

Comments
 (0)