From edf0a75708da66bfb8adaeb6c7a81b347b48be51 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 29 Sep 2022 13:28:03 -0700 Subject: [PATCH 1/7] Add providerId property to low-level locators --- src/client/pythonEnvironments/base/locator.ts | 10 +++++++++- src/client/pythonEnvironments/base/locators.ts | 6 +++++- .../base/locators/composite/envsReducer.ts | 3 ++- .../base/locators/composite/envsResolver.ts | 4 ++-- .../base/locators/lowLevel/condaLocator.ts | 2 ++ .../base/locators/lowLevel/customVirtualEnvLocator.ts | 2 ++ .../base/locators/lowLevel/filesLocator.ts | 7 ++++++- .../lowLevel/globalVirtualEnvronmentLocator.ts | 2 ++ .../base/locators/lowLevel/microsoftStoreLocator.ts | 2 ++ .../base/locators/lowLevel/poetryLocator.ts | 2 ++ .../base/locators/lowLevel/posixKnownPathsLocator.ts | 2 ++ .../base/locators/lowLevel/pyenvLocator.ts | 2 ++ .../base/locators/lowLevel/windowsKnownPathsLocator.ts | 3 +++ .../base/locators/lowLevel/windowsRegistryLocator.ts | 2 ++ .../locators/lowLevel/workspaceVirtualEnvLocator.ts | 2 ++ .../pythonEnvironments/base/locators/wrappers.ts | 2 ++ src/client/pythonEnvironments/index.ts | 6 +++--- src/test/pythonEnvironments/base/common.ts | 2 ++ .../locators/lowLevel/fsWatchingLocator.unit.test.ts | 2 ++ 19 files changed, 54 insertions(+), 9 deletions(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 687348964891..0d6037b4f98e 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -155,6 +155,7 @@ export type BasicEnvInfo = { */ export interface ILocator extends IPythonEnvsWatcher { + readonly providerId: string; /** * Iterate over the enviroments known tos this locator. * @@ -174,6 +175,11 @@ export interface ILocator): IPythonEnvsIterator; } +export type ICompositeLocator = Omit< + ILocator, + 'providerId' +>; + interface IResolver { /** * Find as much info about the given Python environment as possible. @@ -184,7 +190,7 @@ interface IResolver { resolveEnv(path: string): Promise; } -export interface IResolvingLocator extends IResolver, ILocator {} +export interface IResolvingLocator extends IResolver, ICompositeLocator {} export interface GetRefreshEnvironmentsOptions { /** @@ -254,6 +260,8 @@ abstract class LocatorBase { public readonly onChanged: Event; + public abstract readonly providerId: string; + protected readonly emitter: IEmitter; constructor(watcher: IPythonEnvsWatcher & IEmitter) { diff --git a/src/client/pythonEnvironments/base/locators.ts b/src/client/pythonEnvironments/base/locators.ts index 4c854e975ecf..10be15c27bf1 100644 --- a/src/client/pythonEnvironments/base/locators.ts +++ b/src/client/pythonEnvironments/base/locators.ts @@ -5,6 +5,7 @@ import { chain } from '../../common/utils/async'; import { Disposables } from '../../common/utils/resourceLifecycle'; import { PythonEnvInfo } from './info'; import { + ICompositeLocator, ILocator, IPythonEnvsIterator, isProgressEvent, @@ -59,12 +60,15 @@ export function combineIterators(iterators: IPythonEnvsIterator[]): IPytho * * Events and iterator results are combined. */ -export class Locators extends PythonEnvsWatchers implements ILocator { +export class Locators extends PythonEnvsWatchers implements ICompositeLocator { + public readonly providerId: string; + constructor( // The locators will be watched as well as iterated. private readonly locators: ReadonlyArray>, ) { super(locators); + this.providerId = locators.map((loc) => loc.providerId).join('+'); } public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { diff --git a/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts b/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts index 92d81243f97b..2f9bcea8e590 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts @@ -9,6 +9,7 @@ import { areSameEnv } from '../../info/env'; import { getPrioritizedEnvKinds } from '../../info/envKind'; import { BasicEnvInfo, + ICompositeLocator, ILocator, IPythonEnvsIterator, isProgressEvent, @@ -22,7 +23,7 @@ import { PythonEnvsChangedEvent } from '../../watcher'; /** * Combines duplicate environments received from the incoming locator into one and passes on unique environments */ -export class PythonEnvsReducer implements ILocator { +export class PythonEnvsReducer implements ICompositeLocator { public get onChanged(): Event { return this.parentLocator.onChanged; } diff --git a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts index 1baa3f36c993..13cacf337d07 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts @@ -10,7 +10,7 @@ import { getEnvPath, setEnvDisplayString } from '../../info/env'; import { InterpreterInformation } from '../../info/interpreter'; import { BasicEnvInfo, - ILocator, + ICompositeLocator, IPythonEnvsIterator, IResolvingLocator, isProgressEvent, @@ -35,7 +35,7 @@ export class PythonEnvsResolver implements IResolvingLocator { } constructor( - private readonly parentLocator: ILocator, + private readonly parentLocator: ICompositeLocator, private readonly environmentInfoService: IEnvironmentInfoService, ) {} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts index ed7848922893..ef14e85dc354 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts @@ -8,6 +8,8 @@ import { traceError, traceVerbose } from '../../../../logging'; import { FSWatchingLocator } from './fsWatchingLocator'; export class CondaEnvironmentLocator extends FSWatchingLocator { + public readonly providerId: string = 'conda-envs'; + public constructor() { super( () => getCondaEnvironmentsTxt(), diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts index 172a57bb102f..dab15dc95b5c 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts @@ -79,6 +79,8 @@ async function getVirtualEnvKind(interpreterPath: string): Promise { + public readonly providerId: string = 'custom-virtual-envs'; + constructor() { super(getCustomVirtualEnvDirs, getVirtualEnvKind, { // Note detecting kind of virtual env depends on the file structure around the diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts index 9f75f6b6db15..e5ed206650ca 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts @@ -14,7 +14,9 @@ type GetExecutablesFunc = () => AsyncIterableIterator; /** * A naive locator the wraps a function that finds Python executables. */ -class FoundFilesLocator implements ILocator { +abstract class FoundFilesLocator implements ILocator { + public abstract readonly providerId: string; + public readonly onChanged: Event; protected readonly watcher = new PythonEnvsWatcher(); @@ -45,6 +47,8 @@ type GetDirExecutablesFunc = (dir: string) => AsyncIterableIterator; * A locator for executables in a single directory. */ export class DirFilesLocator extends FoundFilesLocator { + public readonly providerId: string; + constructor( dirname: string, defaultKind: PythonEnvKind, @@ -53,6 +57,7 @@ export class DirFilesLocator extends FoundFilesLocator { source?: PythonEnvSource[], ) { super(defaultKind, () => getExecutables(dirname), source); + this.providerId = `dir-files-${dirname}`; } } diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts index e215cdb2a3dd..348a97469a29 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts @@ -83,6 +83,8 @@ async function getVirtualEnvKind(interpreterPath: string): Promise { + public readonly providerId: string = 'global-virtual-env'; + constructor(private readonly searchDepth?: number) { super(getGlobalVirtualEnvDirs, getVirtualEnvKind, { // Note detecting kind of virtual env depends on the file structure around the diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts index d04d85148479..c6cd7b579137 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts @@ -66,6 +66,8 @@ export async function getMicrosoftStorePythonExes(): Promise { } export class MicrosoftStoreLocator extends FSWatchingLocator { + public readonly providerId: string = 'microsoft-store'; + private readonly kind: PythonEnvKind = PythonEnvKind.MicrosoftStore; constructor() { diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts index e37e64347983..ebe729fdb35c 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts @@ -61,6 +61,8 @@ async function getVirtualEnvKind(interpreterPath: string): Promise { + public readonly providerId: string = 'poetry'; + public constructor(private readonly root: string) { super( () => getRootVirtualEnvDir(root), diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts index 02671d27c9bd..25923701c05a 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts @@ -12,6 +12,8 @@ import { isMacDefaultPythonPath } from './macDefaultLocator'; import { traceError } from '../../../../logging'; export class PosixKnownPathsLocator extends Locator { + public readonly providerId = 'posixKnownPaths'; + private kind: PythonEnvKind = PythonEnvKind.OtherGlobal; public iterEnvs(): IPythonEnvsIterator { diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts index 7d6773c0058c..8f328c80bdcb 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts @@ -36,6 +36,8 @@ async function* getPyenvEnvironments(): AsyncIterableIterator { } export class PyenvLocator extends FSWatchingLocator { + public readonly providerId: string = 'pyenv'; + constructor() { super(getPyenvVersionsDir, async () => PythonEnvKind.Pyenv); } diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts index 0f520e8a8876..b7cb27875769 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts @@ -24,6 +24,8 @@ import { DirFilesLocator } from './filesLocator'; * it for changes. */ export class WindowsPathEnvVarLocator implements ILocator, IDisposable { + public readonly providerId: string = 'windows-path-env-var-locator'; + public readonly onChanged: Event; private readonly locators: Locators; @@ -93,6 +95,7 @@ function getDirFilesLocator( yield* await getEnvs(locator.iterEnvs(query)); } return { + providerId: locator.providerId, iterEnvs, dispose, onChanged: locator.onChanged, diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts index 0d44e3c3699e..cb59c1e49a60 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts @@ -7,6 +7,8 @@ import { getRegistryInterpreters } from '../../../common/windowsUtils'; import { traceError } from '../../../../logging'; export class WindowsRegistryLocator extends Locator { + public readonly providerId: string = 'windows-registry'; + // eslint-disable-next-line class-methods-use-this public iterEnvs(): IPythonEnvsIterator { const iterator = async function* () { diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts index 751c9b97c162..bced4b244020 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts @@ -51,6 +51,8 @@ async function getVirtualEnvKind(interpreterPath: string): Promise { + public readonly providerId: string = 'workspaceVirtualEnvLocator'; + public constructor(private readonly root: string) { super( () => getWorkspaceVirtualEnvDirs(this.root), diff --git a/src/client/pythonEnvironments/base/locators/wrappers.ts b/src/client/pythonEnvironments/base/locators/wrappers.ts index fbc21fb44b21..3eacaa74cb48 100644 --- a/src/client/pythonEnvironments/base/locators/wrappers.ts +++ b/src/client/pythonEnvironments/base/locators/wrappers.ts @@ -53,6 +53,8 @@ type WatchRootsFunc = (args: WatchRootsArgs) => IDisposable; */ export class WorkspaceLocators extends LazyResourceBasedLocator { + public readonly providerId: string = 'workspace-locators'; + private readonly locators: Record, IDisposable]> = {}; private readonly roots: Record = {}; diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index b4e9d45fa44a..d67ef02f43ab 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -106,7 +106,7 @@ async function createLocator( // This is shared. ): Promise { // Create the low-level locators. - let locators: ILocator = new ExtensionLocators( + const locators: ILocator = new ExtensionLocators( // Here we pull the locators together. createNonWorkspaceLocators(ext), createWorkspaceLocator(ext), @@ -116,9 +116,9 @@ async function createLocator( const envInfoService = getEnvironmentInfoService(ext.disposables); // Build the stack of composite locators. - locators = new PythonEnvsReducer(locators); + const reducer = new PythonEnvsReducer(locators); const resolvingLocator = new PythonEnvsResolver( - locators, + reducer, // These are shared. envInfoService, ); diff --git a/src/test/pythonEnvironments/base/common.ts b/src/test/pythonEnvironments/base/common.ts index 603f423037bc..1b132df995e0 100644 --- a/src/test/pythonEnvironments/base/common.ts +++ b/src/test/pythonEnvironments/base/common.ts @@ -95,6 +95,8 @@ export function createNamedEnv( } export class SimpleLocator extends Locator { + public readonly providerId: string = 'SimpleLocator'; + private deferred = createDeferred(); constructor( diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts b/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts index 81ff67884f81..0a6d4c9f882a 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts @@ -30,6 +30,8 @@ suite('File System Watching Locator Tests', () => { }); class TestWatcher extends FSWatchingLocator { + public readonly providerId: string = 'test'; + constructor( watcherKind: FSWatcherKind, opts: { From e58f755241a23d86a8c1eeb2e75d34a03bef42ef Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 29 Sep 2022 13:38:50 -0700 Subject: [PATCH 2/7] Add query support for providers --- src/client/pythonEnvironments/base/locator.ts | 4 ++++ src/client/pythonEnvironments/base/locators/wrappers.ts | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 0d6037b4f98e..3f02f60d4fcb 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -128,6 +128,10 @@ export type PythonLocatorQuery = BasicPythonLocatorQuery & { * If provided, results should be limited to within these locations. */ searchLocations?: SearchLocations; + /** + * If provided, results should be limited envs provided by these locators. + */ + providerId?: string; }; type QueryForEvent = E extends PythonEnvsChangedEvent ? PythonLocatorQuery : BasicPythonLocatorQuery; diff --git a/src/client/pythonEnvironments/base/locators/wrappers.ts b/src/client/pythonEnvironments/base/locators/wrappers.ts index 3eacaa74cb48..442ba01f0eb3 100644 --- a/src/client/pythonEnvironments/base/locators/wrappers.ts +++ b/src/client/pythonEnvironments/base/locators/wrappers.ts @@ -30,7 +30,10 @@ export class ExtensionLocators extends Locators { public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { const iterators: IPythonEnvsIterator[] = [this.workspace.iterEnvs(query)]; if (!query?.searchLocations?.doNotIncludeNonRooted) { - iterators.push(...this.nonWorkspace.map((loc) => loc.iterEnvs(query))); + const nonWorkspace = query?.providerId + ? this.nonWorkspace.filter((locator) => query.providerId === locator.providerId) + : this.nonWorkspace; + iterators.push(...nonWorkspace.map((loc) => loc.iterEnvs(query))); } return combineIterators(iterators); } @@ -83,6 +86,10 @@ export class WorkspaceLocators extends LazyResourceBasedLocat // This workspace folder did not match the query, so skip it! return iterEmpty(); } + if (query.providerId && query.providerId !== this.providerId) { + // This is a request for a specific provider, so skip it. + return iterEmpty(); + } } // The query matches or was not location-specific. const [locator] = this.locators[key]; From 4d86099a2ce37539dfa0009356b1dc30b81be53a Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 29 Sep 2022 13:55:15 -0700 Subject: [PATCH 3/7] Hook it up with change event --- src/client/pythonEnvironments/base/locator.ts | 18 ++++++++---------- .../composite/envsCollectionService.ts | 4 +++- .../locators/lowLevel/fsWatchingLocator.ts | 4 ++-- src/client/pythonEnvironments/base/watcher.ts | 15 ++++++++++++--- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 3f02f60d4fcb..ab3b17629bc5 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -7,7 +7,6 @@ import { Event, Uri } from 'vscode'; import { IAsyncIterableIterator, iterEmpty } from '../../common/utils/async'; import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from './info'; import { - BasicPythonEnvsChangedEvent, IPythonEnvsWatcher, PythonEnvCollectionChangedEvent, PythonEnvsChangedEvent, @@ -132,6 +131,10 @@ export type PythonLocatorQuery = BasicPythonLocatorQuery & { * If provided, results should be limited envs provided by these locators. */ providerId?: string; + /** + * If provided, results area limited to this env. + */ + envPath?: string; }; type QueryForEvent = E extends PythonEnvsChangedEvent ? PythonLocatorQuery : BasicPythonLocatorQuery; @@ -157,8 +160,7 @@ export type BasicEnvInfo = { * events emitted via `onChanged` do not need to provide information * for the specific environments that changed. */ -export interface ILocator - extends IPythonEnvsWatcher { +export interface ILocator extends IPythonEnvsWatcher { readonly providerId: string; /** * Iterate over the enviroments known tos this locator. @@ -179,10 +181,7 @@ export interface ILocator): IPythonEnvsIterator; } -export type ICompositeLocator = Omit< - ILocator, - 'providerId' ->; +export type ICompositeLocator = Omit, 'providerId'>; interface IResolver { /** @@ -244,7 +243,7 @@ export interface IDiscoveryAPI { resolveEnv(path: string): Promise; } -interface IEmitter { +interface IEmitter { fire(e: E): void; } @@ -260,8 +259,7 @@ interface IEmitter { * should be used. Only in low-level cases should you consider using * `BasicPythonEnvsChangedEvent`. */ -abstract class LocatorBase - implements ILocator { +abstract class LocatorBase implements ILocator { public readonly onChanged: Event; public abstract readonly providerId: string; diff --git a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts index ca7c93b1c269..7bfaf900f689 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts @@ -57,7 +57,9 @@ export class EnvsCollectionService extends PythonEnvsWatcher { - const query = undefined; // We can also form a query based on the event, but skip that for simplicity. + const query: PythonLocatorQuery | undefined = event.providerId + ? { providerId: event.providerId, envPath: event.envPath } + : undefined; // We can also form a query based on the event, but skip that for simplicity. let scheduledRefresh = this.scheduledRefreshesPerQuery.get(query); // If there is no refresh scheduled for the query, start a new one. if (!scheduledRefresh) { diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts index 6d2c93829906..76522c21718b 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts @@ -135,7 +135,7 @@ export abstract class FSWatchingLocator extends LazyResourceB this.disposables.push( watchLocationForPattern(path.dirname(root), path.basename(root), () => { traceVerbose('Detected change in file: ', root, 'initiating a refresh'); - this.emitter.fire({}); + this.emitter.fire({ providerId: this.providerId }); }), ); return; @@ -161,7 +161,7 @@ export abstract class FSWatchingLocator extends LazyResourceB // |__ python <--- executable const searchLocation = Uri.file(opts.searchLocation ?? path.dirname(getEnvironmentDirFromPath(executable))); traceVerbose('Fired event ', JSON.stringify({ type, kind, searchLocation }), 'from locator'); - this.emitter.fire({ type, kind, searchLocation }); + this.emitter.fire({ type, kind, searchLocation, providerId: this.providerId, envPath: executable }); }; const globs = resolvePythonExeGlobs( diff --git a/src/client/pythonEnvironments/base/watcher.ts b/src/client/pythonEnvironments/base/watcher.ts index dfe998865a28..a9d0ef65595e 100644 --- a/src/client/pythonEnvironments/base/watcher.ts +++ b/src/client/pythonEnvironments/base/watcher.ts @@ -22,11 +22,20 @@ export type BasicPythonEnvsChangedEvent = { /** * The full set of possible info for a Python environments event. - * - * @prop searchLocation - the location, if any, affected by the event */ export type PythonEnvsChangedEvent = BasicPythonEnvsChangedEvent & { + /** + * The location, if any, affected by the event. + */ searchLocation?: Uri; + /** + * A specific provider, if any, affected by the event. + */ + providerId?: string; + /** + * The env, if any, affected by the event. + */ + envPath?: string; }; export type PythonEnvCollectionChangedEvent = BasicPythonEnvCollectionChangedEvent & { @@ -47,7 +56,7 @@ export type BasicPythonEnvCollectionChangedEvent = { * events, their source, and the timing is entirely up to the watcher * implementation. */ -export interface IPythonEnvsWatcher { +export interface IPythonEnvsWatcher { /** * The hook for registering event listeners (callbacks). */ From f5a67f2bf12b76d3dac975011d70470e00690b34 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 29 Sep 2022 15:03:05 -0700 Subject: [PATCH 4/7] Do not have generic locators if not needed --- .../locators/common/resourceBasedLocator.ts | 9 +- .../base/locators/lowLevel/condaLocator.ts | 2 +- .../lowLevel/customVirtualEnvLocator.ts | 2 +- .../locators/lowLevel/fsWatchingLocator.ts | 4 +- .../globalVirtualEnvronmentLocator.ts | 2 +- .../lowLevel/microsoftStoreLocator.ts | 2 +- .../base/locators/lowLevel/poetryLocator.ts | 2 +- .../base/locators/lowLevel/pyenvLocator.ts | 2 +- .../lowLevel/workspaceVirtualEnvLocator.ts | 2 +- .../base/locators/wrappers.ts | 20 +- src/client/pythonEnvironments/index.ts | 4 +- .../lowLevel/fsWatchingLocator.unit.test.ts | 4 +- .../base/locators/lowLevel/index.unit.test.ts | 592 ------------------ 13 files changed, 27 insertions(+), 620 deletions(-) delete mode 100644 src/test/pythonEnvironments/base/locators/lowLevel/index.unit.test.ts diff --git a/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts b/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts index 2a524ad7d28f..a9a7618f4eac 100644 --- a/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts +++ b/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts @@ -5,8 +5,7 @@ import { IDisposable } from '../../../../common/types'; import { createDeferred, Deferred } from '../../../../common/utils/async'; import { Disposables } from '../../../../common/utils/resourceLifecycle'; import { traceError } from '../../../../logging'; -import { PythonEnvInfo } from '../../info'; -import { IPythonEnvsIterator, Locator, PythonLocatorQuery } from '../../locator'; +import { BasicEnvInfo, IPythonEnvsIterator, Locator, PythonLocatorQuery } from '../../locator'; /** * A base locator class that manages the lifecycle of resources. @@ -20,7 +19,7 @@ import { IPythonEnvsIterator, Locator, PythonLocatorQuery } from '../../locator' * * Otherwise it will leak (and we have no leak detection). */ -export abstract class LazyResourceBasedLocator extends Locator implements IDisposable { +export abstract class LazyResourceBasedLocator extends Locator implements IDisposable { protected readonly disposables = new Disposables(); // This will be set only once we have to create necessary resources @@ -42,7 +41,7 @@ export abstract class LazyResourceBasedLocator extends Locato await this.disposables.dispose(); } - public async *iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + public async *iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { await this.activate(); yield* this.doIterEnvs(query); } @@ -50,7 +49,7 @@ export abstract class LazyResourceBasedLocator extends Locato /** * The subclass implementation of iterEnvs(). */ - protected abstract doIterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator; + protected abstract doIterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator; /** * This is where subclasses get their resources ready. diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts index ef14e85dc354..2ef07fef0555 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts @@ -7,7 +7,7 @@ import { Conda, getCondaEnvironmentsTxt } from '../../../common/environmentManag import { traceError, traceVerbose } from '../../../../logging'; import { FSWatchingLocator } from './fsWatchingLocator'; -export class CondaEnvironmentLocator extends FSWatchingLocator { +export class CondaEnvironmentLocator extends FSWatchingLocator { public readonly providerId: string = 'conda-envs'; public constructor() { diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts index dab15dc95b5c..06cbfe38ee3c 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts @@ -78,7 +78,7 @@ async function getVirtualEnvKind(interpreterPath: string): Promise { +export class CustomVirtualEnvironmentLocator extends FSWatchingLocator { public readonly providerId: string = 'custom-virtual-envs'; constructor() { diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts index 76522c21718b..0eb1d125200c 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts @@ -13,7 +13,7 @@ import { resolvePythonExeGlobs, watchLocationForPythonBinaries, } from '../../../common/pythonBinariesWatcher'; -import { PythonEnvInfo, PythonEnvKind } from '../../info'; +import { PythonEnvKind } from '../../info'; import { LazyResourceBasedLocator } from '../common/resourceBasedLocator'; export enum FSWatcherKind { @@ -80,7 +80,7 @@ type FileWatchOptions = { * * Subclasses can call `this.emitter.fire()` * to emit events. */ -export abstract class FSWatchingLocator extends LazyResourceBasedLocator { +export abstract class FSWatchingLocator extends LazyResourceBasedLocator { constructor( /** * Location(s) to watch for python binaries. diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts index 348a97469a29..cddec23f7b0b 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts @@ -82,7 +82,7 @@ async function getVirtualEnvKind(interpreterPath: string): Promise { +export class GlobalVirtualEnvironmentLocator extends FSWatchingLocator { public readonly providerId: string = 'global-virtual-env'; constructor(private readonly searchDepth?: number) { diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts index c6cd7b579137..6b8e5ac0084c 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts @@ -65,7 +65,7 @@ export async function getMicrosoftStorePythonExes(): Promise { return []; } -export class MicrosoftStoreLocator extends FSWatchingLocator { +export class MicrosoftStoreLocator extends FSWatchingLocator { public readonly providerId: string = 'microsoft-store'; private readonly kind: PythonEnvKind = PythonEnvKind.MicrosoftStore; diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts index ebe729fdb35c..5df4f366e86d 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts @@ -60,7 +60,7 @@ async function getVirtualEnvKind(interpreterPath: string): Promise { +export class PoetryLocator extends FSWatchingLocator { public readonly providerId: string = 'poetry'; public constructor(private readonly root: string) { diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts index 8f328c80bdcb..89346069772d 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts @@ -35,7 +35,7 @@ async function* getPyenvEnvironments(): AsyncIterableIterator { } } -export class PyenvLocator extends FSWatchingLocator { +export class PyenvLocator extends FSWatchingLocator { public readonly providerId: string = 'pyenv'; constructor() { diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts index bced4b244020..127515607563 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts @@ -50,7 +50,7 @@ async function getVirtualEnvKind(interpreterPath: string): Promise { +export class WorkspaceVirtualEnvironmentLocator extends FSWatchingLocator { public readonly providerId: string = 'workspaceVirtualEnvLocator'; public constructor(private readonly root: string) { diff --git a/src/client/pythonEnvironments/base/locators/wrappers.ts b/src/client/pythonEnvironments/base/locators/wrappers.ts index 442ba01f0eb3..bfaede584f6f 100644 --- a/src/client/pythonEnvironments/base/locators/wrappers.ts +++ b/src/client/pythonEnvironments/base/locators/wrappers.ts @@ -8,7 +8,7 @@ import { iterEmpty } from '../../../common/utils/async'; import { getURIFilter } from '../../../common/utils/misc'; import { Disposables } from '../../../common/utils/resourceLifecycle'; import { PythonEnvInfo } from '../info'; -import { ILocator, IPythonEnvsIterator, PythonLocatorQuery } from '../locator'; +import { BasicEnvInfo, ILocator, IPythonEnvsIterator, PythonLocatorQuery } from '../locator'; import { combineIterators, Locators } from '../locators'; import { LazyResourceBasedLocator } from './common/resourceBasedLocator'; @@ -38,8 +38,8 @@ export class ExtensionLocators extends Locators { return combineIterators(iterators); } } -type WorkspaceLocatorFactoryResult = ILocator & Partial; -type WorkspaceLocatorFactory = (root: Uri) => WorkspaceLocatorFactoryResult[]; +type WorkspaceLocatorFactoryResult = ILocator & Partial; +type WorkspaceLocatorFactory = (root: Uri) => WorkspaceLocatorFactoryResult[]; type RootURI = string; export type WatchRootsArgs = { @@ -55,14 +55,14 @@ type WatchRootsFunc = (args: WatchRootsArgs) => IDisposable; * The factories are used to produce the locators for each workspace folder. */ -export class WorkspaceLocators extends LazyResourceBasedLocator { +export class WorkspaceLocators extends LazyResourceBasedLocator { public readonly providerId: string = 'workspace-locators'; - private readonly locators: Record, IDisposable]> = {}; + private readonly locators: Record, IDisposable]> = {}; private readonly roots: Record = {}; - constructor(private readonly watchRoots: WatchRootsFunc, private readonly factories: WorkspaceLocatorFactory[]) { + constructor(private readonly watchRoots: WatchRootsFunc, private readonly factories: WorkspaceLocatorFactory[]) { super(); this.activate().ignoreErrors(); } @@ -75,7 +75,7 @@ export class WorkspaceLocators extends LazyResourceBasedLocat roots.forEach((root) => this.removeRoot(root)); } - protected doIterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + protected doIterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { const iterators = Object.keys(this.locators).map((key) => { if (query?.searchLocations !== undefined) { const root = this.roots[key]; @@ -84,11 +84,11 @@ export class WorkspaceLocators extends LazyResourceBasedLocat // Ignore any requests for global envs. if (!query.searchLocations.roots.some(filter)) { // This workspace folder did not match the query, so skip it! - return iterEmpty(); + return iterEmpty(); } if (query.providerId && query.providerId !== this.providerId) { // This is a request for a specific provider, so skip it. - return iterEmpty(); + return iterEmpty(); } } // The query matches or was not location-specific. @@ -117,7 +117,7 @@ export class WorkspaceLocators extends LazyResourceBasedLocat private addRoot(root: Uri): void { // Create the root's locator, wrapping each factory-generated locator. - const locators: ILocator[] = []; + const locators: ILocator[] = []; const disposables = new Disposables(); this.factories.forEach((create) => { create(root).forEach((loc) => { diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index d67ef02f43ab..79022dc09c30 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -177,8 +177,8 @@ function watchRoots(args: WatchRootsArgs): IDisposable { }); } -function createWorkspaceLocator(ext: ExtensionState): WorkspaceLocators { - const locators = new WorkspaceLocators(watchRoots, [ +function createWorkspaceLocator(ext: ExtensionState): WorkspaceLocators { + const locators = new WorkspaceLocators(watchRoots, [ (root: vscode.Uri) => [new WorkspaceVirtualEnvironmentLocator(root.fsPath), new PoetryLocator(root.fsPath)], // Add an ILocator factory func here for each kind of workspace-rooted locator. ]); diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts b/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts index 0a6d4c9f882a..fc1c6927d3fe 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.unit.test.ts @@ -6,7 +6,7 @@ import * as sinon from 'sinon'; import { getOSType, OSType } from '../../../../../client/common/utils/platform'; import { Disposables } from '../../../../../client/common/utils/resourceLifecycle'; import { PythonEnvInfo, PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info'; -import { IPythonEnvsIterator } from '../../../../../client/pythonEnvironments/base/locator'; +import { BasicEnvInfo, IPythonEnvsIterator } from '../../../../../client/pythonEnvironments/base/locator'; import { FSWatcherKind, FSWatchingLocator, @@ -46,7 +46,7 @@ suite('File System Watching Locator Tests', () => { } // eslint-disable-next-line class-methods-use-this - protected doIterEnvs(): IPythonEnvsIterator { + protected doIterEnvs(): IPythonEnvsIterator { throw new Error('Method not implemented.'); } diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/index.unit.test.ts b/src/test/pythonEnvironments/base/locators/lowLevel/index.unit.test.ts deleted file mode 100644 index 284d5b175dbb..000000000000 --- a/src/test/pythonEnvironments/base/locators/lowLevel/index.unit.test.ts +++ /dev/null @@ -1,592 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { expect } from 'chai'; -import { Event, EventEmitter, Uri } from 'vscode'; -import { IDisposable } from '../../../../../client/common/types'; -import { createDeferred } from '../../../../../client/common/utils/async'; -import { PythonEnvInfo, PythonEnvKind } from '../../../../../client/pythonEnvironments/base/info'; -import { WatchRootsArgs, WorkspaceLocators } from '../../../../../client/pythonEnvironments/base/locators/wrappers'; -import { PythonEnvsChangedEvent } from '../../../../../client/pythonEnvironments/base/watcher'; -import { assertSameEnvs, createLocatedEnv, createNamedEnv, getEnvs, SimpleLocator } from '../../common'; - -class WorkspaceFolders { - public added = new EventEmitter(); - - public removed = new EventEmitter(); - - public readonly roots: Uri[]; - - constructor(roots: (Uri | string)[]) { - this.roots = roots.map((r) => (typeof r === 'string' ? Uri.file(r) : r)); - } - - public get onAdded(): Event { - return this.added.event; - } - - public get onRemoved(): Event { - return this.removed.event; - } - - public watchRoots(args: WatchRootsArgs): IDisposable { - const { initRoot, addRoot, removeRoot } = args; - - this.roots.forEach(initRoot); - this.onAdded(addRoot); - this.onRemoved(removeRoot); - - return { - dispose: () => undefined, - }; - } - - public getRootsWatcher(): (args: WatchRootsArgs) => IDisposable { - return (args) => this.watchRoots(args); - } -} - -async function ensureActivated(locators: WorkspaceLocators): Promise { - await getEnvs(locators.iterEnvs()); -} - -suite('WorkspaceLocators', () => { - suite('onChanged', () => { - test('no roots', async () => { - const expected: PythonEnvsChangedEvent[] = []; - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const loc1 = new SimpleLocator([env1]); - const folders = new WorkspaceFolders([]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1]]); - await ensureActivated(locators); - const events: PythonEnvsChangedEvent[] = []; - locators.onChanged((e) => events.push(e)); - const event1: PythonEnvsChangedEvent = { kind: PythonEnvKind.Unknown }; - - loc1.fire(event1); - - expect(events).to.deep.equal(expected); - }); - - test('no factories', async () => { - const expected: PythonEnvsChangedEvent[] = []; - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const loc1 = new SimpleLocator([env1]); - const folders = new WorkspaceFolders(['foo', 'bar']); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), []); - await ensureActivated(locators); - const events: PythonEnvsChangedEvent[] = []; - locators.onChanged((e) => events.push(e)); - const event1: PythonEnvsChangedEvent = { kind: PythonEnvKind.Unknown }; - - loc1.fire(event1); - - expect(events).to.deep.equal(expected); - }); - - test('consolidates events across roots', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const expected: PythonEnvsChangedEvent[] = [ - { searchLocation: root1, kind: PythonEnvKind.Unknown }, - { searchLocation: root2, kind: PythonEnvKind.Venv }, - { searchLocation: root1 }, - { searchLocation: root2, kind: PythonEnvKind.Venv }, - { searchLocation: root2, kind: PythonEnvKind.Pipenv }, - { searchLocation: root1, kind: PythonEnvKind.Conda }, - ]; - const event1: PythonEnvsChangedEvent = { kind: PythonEnvKind.Unknown }; - const event2: PythonEnvsChangedEvent = { kind: PythonEnvKind.Venv }; - const event3: PythonEnvsChangedEvent = {}; - const event4: PythonEnvsChangedEvent = { kind: PythonEnvKind.Venv }; - const event5: PythonEnvsChangedEvent = { kind: PythonEnvKind.Pipenv }; - const event6: PythonEnvsChangedEvent = { kind: PythonEnvKind.Conda }; - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const loc1 = new SimpleLocator([env1]); - const loc2 = new SimpleLocator([]); - const loc3 = new SimpleLocator([]); - const loc4 = new SimpleLocator([]); - const loc5 = new SimpleLocator([]); - const loc6 = new SimpleLocator([]); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc2]), - (r) => (r === root1 ? [loc3] : [loc4, loc5]), - (r) => (r === root1 ? [loc6] : []), - ]); - await ensureActivated(locators); - const events: PythonEnvsChangedEvent[] = []; - locators.onChanged((e) => events.push(e)); - - loc1.fire(event1); - loc2.fire(event2); - loc3.fire(event3); - loc4.fire(event4); - loc5.fire(event5); - loc6.fire(event6); - - expect(events).to.deep.equal(expected); - }); - - test('does not identify roots during init', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - // Force r._formatted to be set. - [root1, root2].forEach((r) => r.toString()); - const folders = new WorkspaceFolders(['foo', 'bar']); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), []); - const events: PythonEnvsChangedEvent[] = []; - locators.onChanged((e) => events.push(e)); - - await ensureActivated(locators); - - expect(events).to.deep.equal([]); - }); - - test('identifies added roots', async () => { - const added = Uri.file('baz'); - const expected: PythonEnvsChangedEvent[] = [{ searchLocation: added }]; - const folders = new WorkspaceFolders(['foo', 'bar']); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), []); - await ensureActivated(locators); - const events: PythonEnvsChangedEvent[] = []; - locators.onChanged((e) => events.push(e)); - - folders.added.fire(added); - - expect(events).to.deep.equal(expected); - }); - - test('identifies removed roots', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - // Force r._formatted to be set. - [root1, root2].forEach((r) => r.toString()); - const expected: PythonEnvsChangedEvent[] = [{ searchLocation: root2 }]; - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), []); - await ensureActivated(locators); - const events: PythonEnvsChangedEvent[] = []; - locators.onChanged((e) => events.push(e)); - - folders.removed.fire(root2); - - expect(events).to.deep.equal(expected); - }); - - test('does not emit events from removed roots', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const expected: PythonEnvsChangedEvent[] = [ - { searchLocation: root1, kind: PythonEnvKind.Unknown }, - { searchLocation: root2, kind: PythonEnvKind.Venv }, - { searchLocation: root2 }, // removed - { searchLocation: root1 }, - ]; - const event1: PythonEnvsChangedEvent = { kind: PythonEnvKind.Unknown }; - const event2: PythonEnvsChangedEvent = { kind: PythonEnvKind.Venv }; - const event3: PythonEnvsChangedEvent = {}; - const event4: PythonEnvsChangedEvent = { kind: PythonEnvKind.Venv }; - const loc1 = new SimpleLocator([]); - const loc2 = new SimpleLocator([]); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [(r) => (r === root1 ? [loc1] : [loc2])]); - await ensureActivated(locators); - const events: PythonEnvsChangedEvent[] = []; - locators.onChanged((e) => events.push(e)); - - loc1.fire(event1); - loc2.fire(event2); - folders.removed.fire(root2); - loc1.fire(event3); - loc2.fire(event4); // This one is ignored. - - expect(events).to.deep.equal(expected); - }); - }); - - suite('iterEnvs()', () => { - test('no roots', async () => { - const expected: PythonEnvInfo[] = []; - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const loc1 = new SimpleLocator([env1]); - const folders = new WorkspaceFolders([]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1]]); - await ensureActivated(locators); - - const iterators = locators.iterEnvs(); - const envs = await getEnvs(iterators); - - expect(envs).to.deep.equal(expected); - }); - - test('no factories', async () => { - const expected: PythonEnvInfo[] = []; - const folders = new WorkspaceFolders(['foo', 'bar']); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), []); - await ensureActivated(locators); - - const iterators = locators.iterEnvs(); - const envs = await getEnvs(iterators); - - expect(envs).to.deep.equal(expected); - }); - - test('one empty', async () => { - const root1 = Uri.file('foo'); - const expected: PythonEnvInfo[] = []; - const loc1 = new SimpleLocator([]); - const folders = new WorkspaceFolders([root1]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1]]); - await ensureActivated(locators); - - const iterators = locators.iterEnvs(); - const envs = await getEnvs(iterators); - - expect(envs).to.deep.equal(expected); - }); - - test('one not empty', async () => { - const root1 = Uri.file('foo'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const expected: PythonEnvInfo[] = [env1]; - const loc1 = new SimpleLocator([env1]); - const folders = new WorkspaceFolders([root1]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1]]); - await ensureActivated(locators); - - const iterators = locators.iterEnvs(); - const envs = await getEnvs(iterators); - - assertSameEnvs(envs, expected); - }); - - test('empty locator ignored', async () => { - const root1 = Uri.file('foo'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); - const expected: PythonEnvInfo[] = [env1, env2]; - const loc1 = new SimpleLocator([env1]); - const loc2 = new SimpleLocator([], { before: () => loc1.done }); - const loc3 = new SimpleLocator([env2], { before: () => loc2.done }); - const folders = new WorkspaceFolders([root1]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [() => [loc1, loc2, loc3]]); - await ensureActivated(locators); - - const iterators = locators.iterEnvs(); - const envs = await getEnvs(iterators); - - assertSameEnvs(envs, expected); - }); - - test('consolidates envs across roots', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env6 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const env7 = createNamedEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); - const env8 = createNamedEnv('foo', '3.5.12b1', PythonEnvKind.Venv); - const expected: PythonEnvInfo[] = [env1, env2, env3, env4, env5, env6, env7, env8]; - const loc1 = new SimpleLocator([env1, env2]); - const loc2 = new SimpleLocator([env3, env4], { before: () => loc1.done }); - const loc3 = new SimpleLocator([env5, env6], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env7, env8], { before: () => loc3.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - - const iterators = locators.iterEnvs(); - const envs = await getEnvs(iterators); - - assertSameEnvs(envs, expected); - }); - - test('query matches a root', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env3 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env4 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const expected: PythonEnvInfo[] = [env1, env2]; - const loc1 = new SimpleLocator([env1]); - const loc2 = new SimpleLocator([env2], { before: () => loc1.done }); - const loc3 = new SimpleLocator([env3], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env4], { before: () => loc3.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - const query = { searchLocations: { roots: [root1] } }; - - const iterators = locators.iterEnvs(query); - const envs = await getEnvs(iterators); - - assertSameEnvs(envs, expected); - }); - - test('query matches all roots', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env3 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env4 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const expected: PythonEnvInfo[] = [env1, env2, env3, env4]; - const loc1 = new SimpleLocator([env1]); - const loc2 = new SimpleLocator([env2], { before: () => loc1.done }); - const loc3 = new SimpleLocator([env3], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env4], { before: () => loc3.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - const query = { searchLocations: { roots: [root1, root2] } }; - - const iterators = locators.iterEnvs(query); - const envs = await getEnvs(iterators); - - assertSameEnvs(envs, expected); - }); - - test('query does not match a root', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env3 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env4 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const loc1 = new SimpleLocator([env1]); - const loc2 = new SimpleLocator([env2], { before: () => loc1.done }); - const loc3 = new SimpleLocator([env3], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env4], { before: () => loc3.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - const query = { searchLocations: { roots: [Uri.file('baz')] } }; - - const iterators = locators.iterEnvs(query); - const envs = await getEnvs(iterators); - - expect(envs).to.deep.equal([]); - }); - - test('query has no searchLocation', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env3 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env4 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const expected: PythonEnvInfo[] = [env1, env2, env3, env4]; - const loc1 = new SimpleLocator([env1]); - const loc2 = new SimpleLocator([env2], { before: () => loc1.done }); - const loc3 = new SimpleLocator([env3], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env4], { before: () => loc3.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - - const iterators = locators.iterEnvs({ kinds: [PythonEnvKind.Unknown] }); - const envs = await getEnvs(iterators); - - assertSameEnvs(envs, expected); - }); - - test('iterate out of order', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env6 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const env7 = createNamedEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); - const env8 = createNamedEnv('foo', '3.5.12b1', PythonEnvKind.Venv); - const expected: PythonEnvInfo[] = [env5, env6, env1, env2, env3, env4, env7, env8]; - const loc3 = new SimpleLocator([env5, env6]); - const loc1 = new SimpleLocator([env1, env2], { before: () => loc3.done }); - const loc2 = new SimpleLocator([env3, env4], { before: () => loc1.done }); - const loc4 = new SimpleLocator([env7, env8], { before: () => loc2.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - - const iterators = locators.iterEnvs(); - const envs = await getEnvs(iterators); - - assertSameEnvs(envs, expected); - }); - - test('iterate intermingled', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo-x', '3.8.1', PythonEnvKind.Venv); - const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env6 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const env7 = createNamedEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); - const env8 = createNamedEnv('foo-y', '3.5.12b1', PythonEnvKind.Venv); - const expected = [env3, env6, env1, env2, env8, env4, env5, env7]; - const ordered = [env1, env2, env3, env4, env5, env6, env7, env8]; - const deferreds = [ - createDeferred(), - createDeferred(), - createDeferred(), - createDeferred(), - createDeferred(), - createDeferred(), - createDeferred(), - createDeferred(), - ]; - async function beforeEach(env: PythonEnvInfo) { - const index = expected.indexOf(env); - if (index === 0) { - return; - } - const blockedBy = ordered.indexOf(expected[index - 1]); - await deferreds[blockedBy].promise; - } - async function afterEach(env: PythonEnvInfo) { - const index = ordered.indexOf(env); - deferreds[index].resolve(); - } - const loc1 = new SimpleLocator([env1, env2], { beforeEach, afterEach }); - const loc2 = new SimpleLocator([env3, env4], { beforeEach, afterEach }); - const loc3 = new SimpleLocator([env5, env6], { beforeEach, afterEach }); - const loc4 = new SimpleLocator([env7, env8], { beforeEach, afterEach }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - - const iterator = locators.iterEnvs(); - const envs = await getEnvs(iterator); - - assertSameEnvs(envs, expected); - }); - - test('respects roots set during init', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env6 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const env7 = createNamedEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); - const env8 = createNamedEnv('foo', '3.5.12b1', PythonEnvKind.Venv); - const expected: PythonEnvInfo[] = [env1, env2, env3, env4, env5, env6, env7, env8]; - const loc1 = new SimpleLocator([env1, env2]); - const loc2 = new SimpleLocator([env3, env4], { before: () => loc1.done }); - const loc3 = new SimpleLocator([env5, env6], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env7, env8], { before: () => loc3.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - - const iterator = locators.iterEnvs(); - const envs = await getEnvs(iterator); - - assertSameEnvs(envs, expected); - }); - - test('respects added roots', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env6 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const env7 = createNamedEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); - const env8 = createNamedEnv('foo', '3.5.12b1', PythonEnvKind.Venv); - const expected: PythonEnvInfo[] = [env1, env2, env3, env4, env5, env6, env7, env8]; - const loc1 = new SimpleLocator([env1, env2]); - const loc2 = new SimpleLocator([env3, env4], { before: () => loc1.done }); - const loc3 = new SimpleLocator([env5, env6], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env7, env8], { before: () => loc3.done }); - const folders = new WorkspaceFolders([]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - - const iteratorBefore = locators.iterEnvs(); - const envsBefore = await getEnvs(iteratorBefore); - folders.added.fire(root1); - folders.added.fire(root2); - const iteratorAfter = locators.iterEnvs(); - const envsAfter = await getEnvs(iteratorAfter); - - expect(envsBefore).to.deep.equal([]); - expect(envsAfter).to.deep.equal(expected); - }); - - test('ignores removed roots', async () => { - const root1 = Uri.file('foo'); - const root2 = Uri.file('bar'); - const env1 = createNamedEnv('foo', '3.8.1', PythonEnvKind.Venv); - const env2 = createLocatedEnv('foo/some-dir', '3.8.1', PythonEnvKind.Conda); - const env3 = createNamedEnv('python2', '2.7', PythonEnvKind.Pipenv); - const env4 = createNamedEnv('42', '3.9.0rc2', PythonEnvKind.Pyenv); - const env5 = createNamedEnv('hello world', '3.8', PythonEnvKind.VirtualEnv); - const env6 = createNamedEnv('spam', '3.10.0a0', PythonEnvKind.OtherVirtual); - const env7 = createNamedEnv('eggs', '3.9.1a0', PythonEnvKind.Venv); - const env8 = createNamedEnv('foo', '3.5.12b1', PythonEnvKind.Venv); - const expectedBefore = [env1, env2, env3, env4, env5, env6, env7, env8]; - const expectedAfter = [env1, env2, env3, env4]; - const loc1 = new SimpleLocator([env1, env2]); - const loc2 = new SimpleLocator([env3, env4], { before: () => loc1.done }); - const loc3 = new SimpleLocator([env5, env6], { before: () => loc2.done }); - const loc4 = new SimpleLocator([env7, env8], { before: () => loc3.done }); - const folders = new WorkspaceFolders([root1, root2]); - const locators = new WorkspaceLocators(folders.getRootsWatcher(), [ - (r) => (r === root1 ? [loc1] : [loc3]), - (r) => (r === root1 ? [loc2] : [loc4]), - ]); - await ensureActivated(locators); - - const iteratorBefore = locators.iterEnvs(); - const envsBefore = await getEnvs(iteratorBefore); - folders.removed.fire(root2); - const iteratorAfter = locators.iterEnvs(); - const envsAfter = await getEnvs(iteratorAfter); - - assertSameEnvs(envsBefore, expectedBefore); - assertSameEnvs(envsAfter, expectedAfter); - }); - }); -}); From 76092550c12604cfb19fe9caad80c7c2a2a882dc Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 29 Sep 2022 15:05:22 -0700 Subject: [PATCH 5/7] Add support for querying a particular env path for watcher locators --- .../locators/common/resourceBasedLocator.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts b/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts index a9a7618f4eac..bbec962f75a2 100644 --- a/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts +++ b/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts @@ -5,6 +5,8 @@ import { IDisposable } from '../../../../common/types'; import { createDeferred, Deferred } from '../../../../common/utils/async'; import { Disposables } from '../../../../common/utils/resourceLifecycle'; import { traceError } from '../../../../logging'; +import { arePathsSame } from '../../../common/externalDependencies'; +import { getEnvPath } from '../../info/env'; import { BasicEnvInfo, IPythonEnvsIterator, Locator, PythonLocatorQuery } from '../../locator'; /** @@ -42,8 +44,21 @@ export abstract class LazyResourceBasedLocator extends Locator imp } public async *iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { - await this.activate(); - yield* this.doIterEnvs(query); + const iterator = this.doIterEnvs(query); + if (query?.envPath) { + let result = await iterator.next(); + while (!result.done) { + const currEnv = result.value; + const { path } = getEnvPath(currEnv.executablePath, currEnv.envPath); + if (arePathsSame(path, query.envPath)) { + yield currEnv; + break; + } + result = await iterator.next(); + } + } else { + yield* iterator; + } } /** From 4bd26db5ad1cdaf7c575a0192fd06655b33f8fc6 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 6 Oct 2022 15:26:42 -0700 Subject: [PATCH 6/7] Fix conda watcher test --- .../base/locators/lowLevel/condaLocator.testvirtualenvs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/condaLocator.testvirtualenvs.ts b/src/test/pythonEnvironments/base/locators/lowLevel/condaLocator.testvirtualenvs.ts index 19db5d9f34b1..1b2ea8715d35 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/condaLocator.testvirtualenvs.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/condaLocator.testvirtualenvs.ts @@ -84,7 +84,7 @@ suite('Conda Env Watcher', async () => { test('Fires when conda `environments.txt` file is created', async () => { let actualEvent: PythonEnvsChangedEvent; const deferred = createDeferred(); - const expectedEvent = {}; + const expectedEvent = { providerId: 'conda-envs' }; await setupLocator(async (e) => { deferred.resolve(); actualEvent = e; @@ -99,7 +99,7 @@ suite('Conda Env Watcher', async () => { test('Fires when conda `environments.txt` file is updated', async () => { let actualEvent: PythonEnvsChangedEvent; const deferred = createDeferred(); - const expectedEvent = {}; + const expectedEvent = { providerId: 'conda-envs' }; await condaEnvsTxt.create(); await setupLocator(async (e) => { deferred.resolve(); From 7bb72eb649396cd04984fe09a164d8b7fdad9848 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Tue, 11 Oct 2022 10:01:12 -0700 Subject: [PATCH 7/7] Oops --- .../base/locators/common/resourceBasedLocator.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts b/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts index bbec962f75a2..c370c36ae190 100644 --- a/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts +++ b/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts @@ -44,6 +44,7 @@ export abstract class LazyResourceBasedLocator extends Locator imp } public async *iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + await this.activate(); const iterator = this.doIterEnvs(query); if (query?.envPath) { let result = await iterator.next();