From 65852fa730e922b1acad1f5f1133a0a1beb34da8 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 21 Jul 2022 17:02:45 -0700 Subject: [PATCH 01/32] Update --- src/client/apiTypes.ts | 231 ++++++++++++------ src/client/proposedApi.ts | 2 +- .../pythonEnvironments/base/info/index.ts | 2 +- src/client/pythonEnvironments/base/locator.ts | 3 +- src/client/pythonEnvironments/converter.ts | 126 ++++++++++ src/client/pythonEnvironments/info/index.ts | 14 +- 6 files changed, 295 insertions(+), 83 deletions(-) create mode 100644 src/client/pythonEnvironments/converter.ts diff --git a/src/client/apiTypes.ts b/src/client/apiTypes.ts index 847b215bce49..ce718a144c02 100644 --- a/src/client/apiTypes.ts +++ b/src/client/apiTypes.ts @@ -1,11 +1,17 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { Event, Uri } from 'vscode'; import { Resource } from './common/types'; +import { Architecture } from './common/utils/platform'; import { IDataViewerDataProvider, IJupyterUriProvider } from './jupyter/types'; -import { EnvPathType, PythonEnvKind } from './pythonEnvironments/base/info'; -import { GetRefreshEnvironmentsOptions, ProgressNotificationEvent } from './pythonEnvironments/base/locator'; +import { EnvPathType } from './pythonEnvironments/base/info'; +import { + GetRefreshEnvironmentsOptions, + IPythonEnvsIterator, + ProgressNotificationEvent, +} from './pythonEnvironments/base/locator'; /* * Do not introduce any breaking changes to this API. @@ -91,21 +97,55 @@ export interface IExtensionApi { export interface EnvironmentDetailsOptions { useCache: boolean; + // noShell?: boolean; // For execution without shell } export interface EnvironmentDetails { - interpreterPath: string; - envFolderPath?: string; - version: string[]; - environmentType: PythonEnvKind[]; - metadata: Record; + executable: { + path: string; + run: string[]; + env?: any; + shell?: boolean; + shellCommand?: Record<'cmd' | 'fish' | 'bash' | 'string', { run: string[] }>; + bitness?: Architecture; + cwd?: string; + sysPrefix: string; + }; + environment?: { + type: EnvType; + name?: string; + path: string; + project?: string; // Any specific project environment is created for. + }; + version: { + major: number; + minor: number; + micro: number; + releaselevel: 'alpha' | 'beta' | 'candidate' | 'final'; + serial: number; + sysVersion?: string; + }; + implementation?: { + // `sys.implementation` + name: string; + version: { + major: number; + minor: number; + micro: number; + releaselevel: 'alpha' | 'beta' | 'candidate' | 'final'; + serial: number; + }; + }; + environmentSource: EnvSource[]; + // Are the results specific to the environment (variables, working directory, etc.)? + contextSensitive: boolean; } export interface EnvironmentsChangedParams { /** * Path to environment folder or path to interpreter that uniquely identifies an environment. - * Virtual environments lacking an interpreter are identified by environment folder paths, - * whereas other envs can be identified using interpreter path. + * Environments lacking an interpreter are identified by environment folder paths, + * whereas other envs can be identified using executable path. */ path?: string; type: 'add' | 'remove' | 'update' | 'clear-all'; @@ -114,8 +154,8 @@ export interface EnvironmentsChangedParams { export interface ActiveEnvironmentChangedParams { /** * Path to environment folder or path to interpreter that uniquely identifies an environment. - * Virtual environments lacking an interpreter are identified by environment folder paths, - * whereas other envs can be identified using interpreter path. + * Environments lacking an interpreter are identified by environment folder paths, + * whereas other envs can be identified using executable path. */ path: string; resource?: Uri; @@ -128,33 +168,13 @@ export interface RefreshEnvironmentsOptions { export interface IProposedExtensionAPI { environment: { /** - * An event that is emitted when execution details (for a resource) change. For instance, when interpreter configuration changes. + * This event is triggered when the active environment changes. */ - readonly onDidChangeExecutionDetails: Event; + onDidActiveEnvironmentChanged: Event; /** - * Returns all the details the consumer needs to execute code within the selected environment, - * corresponding to the specified resource taking into account any workspace-specific settings - * for the workspace to which this resource belongs. - * @param {Resource} [resource] A resource for which the setting is asked for. - * * When no resource is provided, the setting scoped to the first workspace folder is returned. - * * If no folder is present, it returns the global setting. - * @returns {({ execCommand: string[] | undefined })} + * An event that is emitted when execution details (for a resource) change. For instance, when interpreter configuration changes. */ - getExecutionDetails( - resource?: Resource, - ): Promise<{ - /** - * E.g of execution commands returned could be, - * * `['']` - * * `['']` - * * `['conda', 'run', 'python']` which is used to run from within Conda environments. - * or something similar for some other Python environments. - * - * @type {(string[] | undefined)} When return value is `undefined`, it means no interpreter is set. - * Otherwise, join the items returned using space to construct the full execution command. - */ - execCommand: string[] | undefined; - }>; + readonly onDidChangeExecutionDetails: Event; /** * Returns the path to the python binary selected by the user or as in the settings. * This is just the path to the python binary, this does not provide activation or any @@ -177,16 +197,6 @@ export interface IProposedExtensionAPI { path: string, options?: EnvironmentDetailsOptions, ): Promise; - /** - * Returns paths to environments that uniquely identifies an environment found by the extension - * at the time of calling. This API will *not* trigger a refresh. If a refresh is going on it - * will *not* wait for the refresh to finish. This will return what is known so far. To get - * complete list `await` on promise returned by `getRefreshPromise()`. - * - * Virtual environments lacking an interpreter are identified by environment folder paths, - * whereas other envs can be identified using interpreter path. - */ - getEnvironmentPaths(): Promise; /** * Sets the active environment path for the python extension for the resource. Configuration target * will always be the workspace folder. @@ -195,34 +205,107 @@ export interface IProposedExtensionAPI { * folder. */ setActiveEnvironment(path: string, resource?: Resource): Promise; - /** - * This API will re-trigger environment discovery. Extensions can wait on the returned - * promise to get the updated environment list. If there is a refresh already going on - * then it returns the promise for that refresh. - * @param options : [optional] - * * clearCache : When true, this will clear the cache before environment refresh - * is triggered. - */ - refreshEnvironment(options?: RefreshEnvironmentsOptions): Promise; - /** - * Tracks discovery progress for current list of known environments, i.e when it starts, finishes or any other relevant - * stage. Note the progress for a particular query is currently not tracked or reported, this only indicates progress of - * the entire collection. - */ - readonly onRefreshProgress: Event; - /** - * Returns a promise for the ongoing refresh. Returns `undefined` if there are no active - * refreshes going on. - */ - getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise | undefined; - /** - * This event is triggered when the known environment list changes, like when a environment - * is found, existing environment is removed, or some details changed on an environment. - */ - onDidEnvironmentsChanged: Event; - /** - * This event is triggered when the active environment changes. - */ - onDidActiveEnvironmentChanged: Event; + locator: { + /** + * Returns paths to environments that uniquely identifies an environment found by the extension + * at the time of calling. This API will *not* trigger a refresh. If a refresh is going on it + * will *not* wait for the refresh to finish. This will return what is known so far. To get + * complete list `await` on promise returned by `getRefreshPromise()`. + * + * Environments lacking an interpreter are identified by environment folder paths, + * whereas other envs can be identified using executable path. + */ + getEnvironmentPaths(): Promise; + /** + * This event is triggered when the known environment list changes, like when a environment + * is found, existing environment is removed, or some details changed on an environment. + */ + onDidEnvironmentsChanged: Event; + /** + * This API will re-trigger environment discovery. Extensions can wait on the returned + * promise to get the updated environment list. If there is a refresh already going on + * then it returns the promise for that refresh. + * @param options : [optional] + * * clearCache : When true, this will clear the cache before environment refresh + * is triggered. + */ + refreshEnvironment(options?: RefreshEnvironmentsOptions): Promise; + /** + * Returns a promise for the ongoing refresh. Returns `undefined` if there are no active + * refreshes going on. + */ + getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise | undefined; + /** + * Tracks discovery progress for current list of known environments, i.e when it starts, finishes or any other relevant + * stage. Note the progress for a particular query is currently not tracked or reported, this only indicates progress of + * the entire collection. + */ + readonly onRefreshProgress: Event; + }; + registerEnvironmentProvider( + environmentProvider: IEnvironmentProvider, + metadata: EnvironmentProviderMetadata, + ): Promise; }; } + +interface IEnvironmentProvider { + locator: ILocatorClass; + getEnvironmentDetails: (env: EnvInfo) => Promise; +} + +export interface ILocatorClass { + new (root?: string): ILocatorAPI; +} + +export interface ILocatorAPI { + iterEnvs?(): IPythonEnvsIterator; + readonly onChanged?: Event; +} + +export type EnvInfo = { + envSources: EnvSource[]; + executablePath: string; + envPath?: string; +}; + +/** + * These can be used when querying for a particular env. + */ +interface EnvironmentProviderMetadata { + readonly envType: EnvType; + readonly searchLocation?: string; + readonly envSources: EnvSource[]; + readonly isRootBasedLocator: boolean; +} + +type EnvironmentMetaData = EnvironmentProviderMetadata; + +export interface LocatorEnvsChangedEvent { + /** + * Any details known about the environment which can be used for query. + */ + env?: EnvironmentMetaData; + type: EnvChangeType; +} + +export type EnvChangeType = 'add' | 'remove' | 'update'; + +export enum EnvType { + VirtualEnv = 'VirtualEnv', + Conda = 'Conda', + Unknown = 'Unknown', + Global = 'GlobalInterpreter', +} + +export enum EnvSource { + Conda = 'Conda', + Pipenv = 'PipEnv', + Poetry = 'Poetry', + VirtualEnv = 'VirtualEnv', + Venv = 'Venv', + VirtualEnvWrapper = 'VirtualEnvWrapper', + WindowsStore = 'WindowsStore', + Pyenv = 'Pyenv', + Custom = 'Custom', +} diff --git a/src/client/proposedApi.ts b/src/client/proposedApi.ts index e4ac6fd83caa..8ed307fb8872 100644 --- a/src/client/proposedApi.ts +++ b/src/client/proposedApi.ts @@ -33,7 +33,7 @@ function getVersionString(env: PythonEnvInfo): string[] { if (env.version.release) { ver.push(`${env.version.release}`); if (env.version.sysVersion) { - ver.push(`${env.version.release}`); + ver.push(`${env.version.sysVersion}`); } } return ver; diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index 85b574713005..c7bcb060a221 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -29,7 +29,7 @@ export enum PythonEnvKind { export interface EnvPathType { /** * Path to environment folder or path to interpreter that uniquely identifies an environment. - * Virtual environments lacking an interpreter are identified by environment folder paths, + * Environments lacking an interpreter are identified by environment folder paths, * whereas other envs can be identified using interpreter path. */ path: string; diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 0e0347229df9..aa6394d5241b 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -153,8 +153,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 { /** * Iterate over the enviroments known tos this locator. * diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts new file mode 100644 index 000000000000..07f381606f85 --- /dev/null +++ b/src/client/pythonEnvironments/converter.ts @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { EventEmitter, Event } from 'vscode'; +import { EnvChangeType, ILocatorAPI, LocatorEnvsChangedEvent, EnvInfo } from '../apiTypes'; +import { FileChangeType } from '../common/platform/fileSystemWatcher'; +import { traceVerbose } from '../logging'; +import { PythonEnvKind } from './base/info'; +import { + ILocator, + BasicEnvInfo, + IPythonEnvsIterator, + PythonEnvUpdatedEvent, + ProgressNotificationEvent, + isProgressEvent, + ProgressReportStage, +} from './base/locator'; +import { PythonEnvsChangedEvent } from './base/watcher'; + +/** + * Converts the proposed interface into a class implementing basic interface. + * ILocator ======> ILocator + */ +export class CustomLocator implements ILocator { + private readonly didChange = new EventEmitter(); + + private eventKeys: Record = { + add: FileChangeType.Created, + remove: FileChangeType.Deleted, + update: FileChangeType.Changed, + }; + + public get onChanged(): Event { + return this.didChange.event; + } + + constructor(private readonly parentLocator: ILocatorAPI) { + if (parentLocator.onChanged) { + parentLocator.onChanged((e: LocatorEnvsChangedEvent) => { + const event: PythonEnvsChangedEvent = { type: this.eventKeys[`${e.type}`] }; + // TODO: Add translation for other events. + this.didChange.fire(event); + }); + } + } + + public iterEnvs(): IPythonEnvsIterator { + const didUpdate = new EventEmitter | ProgressNotificationEvent>(); + const incomingIterator = this.parentLocator.iterEnvs!(); + const iterator = iterEnvsIterator(incomingIterator, didUpdate); + iterator.onUpdated = didUpdate.event; + return iterator; + } +} + +async function* iterEnvsIterator( + iterator: IPythonEnvsIterator, + didUpdate: EventEmitter | ProgressNotificationEvent>, +): IPythonEnvsIterator { + const state = { + done: false, + pending: 0, + }; + const seen: BasicEnvInfo[] = []; + + if (iterator.onUpdated !== undefined) { + const listener = iterator.onUpdated((event) => { + state.pending += 1; + if (isProgressEvent(event)) { + if (event.stage === ProgressReportStage.discoveryFinished) { + state.done = true; + listener.dispose(); + } else { + didUpdate.fire(event); + } + } else if (event.update === undefined) { + throw new Error( + 'Unsupported behavior: `undefined` environment updates are not supported from downstream locators in reducer', + ); + } else if (seen[event.index] !== undefined) { + const oldEnv = seen[event.index]; + seen[event.index] = convertToBasicEnv(event.update); + didUpdate.fire({ index: event.index, old: oldEnv, update: convertToBasicEnv(event.update) }); + } else { + // This implies a problem in a downstream locator + traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`); + } + state.pending -= 1; + checkIfFinishedAndNotify(state, didUpdate); + }); + } else { + didUpdate.fire({ stage: ProgressReportStage.discoveryStarted }); + } + + let result = await iterator.next(); + while (!result.done) { + const currEnv = convertToBasicEnv(result.value); + yield currEnv; + seen.push(currEnv); + result = await iterator.next(); + } + if (iterator.onUpdated === undefined) { + state.done = true; + checkIfFinishedAndNotify(state, didUpdate); + } +} + +/** + * When all info from incoming iterator has been received and all background calls finishes, notify that we're done + * @param state Carries the current state of progress + * @param didUpdate Used to notify when finished + */ +function checkIfFinishedAndNotify( + state: { done: boolean; pending: number }, + didUpdate: EventEmitter | ProgressNotificationEvent>, +) { + if (state.done && state.pending === 0) { + didUpdate.fire({ stage: ProgressReportStage.discoveryFinished }); + didUpdate.dispose(); + } +} + +function convertToBasicEnv(env: EnvInfo): BasicEnvInfo { + // TODO: Use kind converter + return { executablePath: env.executablePath, envPath: env.envPath, kind: PythonEnvKind.Unknown }; +} diff --git a/src/client/pythonEnvironments/info/index.ts b/src/client/pythonEnvironments/info/index.ts index ef7e1c5ac791..22bf95c19e6f 100644 --- a/src/client/pythonEnvironments/info/index.ts +++ b/src/client/pythonEnvironments/info/index.ts @@ -10,17 +10,21 @@ import { PythonVersion } from './pythonVersion'; * The supported Python environment types. */ export enum EnvironmentType { - Unknown = 'Unknown', Conda = 'Conda', - VirtualEnv = 'VirtualEnv', + // Complex virtual envs Pipenv = 'PipEnv', - Pyenv = 'Pyenv', - Venv = 'Venv', - WindowsStore = 'WindowsStore', Poetry = 'Poetry', + // Simple virtual envs + VirtualEnv = 'VirtualEnv', + Venv = 'Venv', VirtualEnvWrapper = 'VirtualEnvWrapper', + // Global Global = 'Global', System = 'System', + WindowsStore = 'WindowsStore', + Unknown = 'Unknown', + // Pyenv + Pyenv = 'Pyenv', } export const virtualEnvTypes = [ From 3e509b8385db6c0dd43a28cb98e50f87e3daeb4d Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 22 Jul 2022 15:48:39 -0700 Subject: [PATCH 02/32] Add API to contribute a new locator --- .../pythonEnvironments/base/locators.ts | 11 +++- .../base/locators/wrappers.ts | 60 +++++++++++++------ .../pythonEnvironments/base/watchers.ts | 6 ++ src/client/pythonEnvironments/index.ts | 10 ++-- 4 files changed, 60 insertions(+), 27 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators.ts b/src/client/pythonEnvironments/base/locators.ts index 4c854e975ecf..886f4f6e1258 100644 --- a/src/client/pythonEnvironments/base/locators.ts +++ b/src/client/pythonEnvironments/base/locators.ts @@ -3,8 +3,8 @@ import { chain } from '../../common/utils/async'; import { Disposables } from '../../common/utils/resourceLifecycle'; -import { PythonEnvInfo } from './info'; import { + BasicEnvInfo, ILocator, IPythonEnvsIterator, isProgressEvent, @@ -59,10 +59,10 @@ 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 ILocator { constructor( // The locators will be watched as well as iterated. - private readonly locators: ReadonlyArray>, + private locators: ILocator[], ) { super(locators); } @@ -71,4 +71,9 @@ export class Locators extends PythonEnvsWatchers implements I const iterators = this.locators.map((loc) => loc.iterEnvs(query)); return combineIterators(iterators); } + + public addLocator(locator: ILocator): void { + this.locators = [...this.locators, locator]; + this.addWatcher(locator); + } } diff --git a/src/client/pythonEnvironments/base/locators/wrappers.ts b/src/client/pythonEnvironments/base/locators/wrappers.ts index 9313ade20218..57bd64d7d302 100644 --- a/src/client/pythonEnvironments/base/locators/wrappers.ts +++ b/src/client/pythonEnvironments/base/locators/wrappers.ts @@ -3,12 +3,14 @@ // eslint-disable-next-line max-classes-per-file import { Uri } from 'vscode'; +import { ILocatorClass } from '../../../apiTypes'; import { IDisposable } from '../../../common/types'; import { iterEmpty } from '../../../common/utils/async'; import { getURIFilter } from '../../../common/utils/misc'; import { Disposables } from '../../../common/utils/resourceLifecycle'; +import { CustomLocator } from '../../converter'; 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'; @@ -16,24 +18,33 @@ import { LazyResourceBasedLocator } from './common/resourceBasedLocator'; * A wrapper around all locators used by the extension. */ -export class ExtensionLocators extends Locators { +export class ExtensionLocators extends Locators { constructor( // These are expected to be low-level locators (e.g. system). - private readonly nonWorkspace: ILocator[], + private nonWorkspace: ILocator[], // This is expected to be a locator wrapping any found in // the workspace (i.e. WorkspaceLocators). - private readonly workspace: ILocator, + private workspace: WorkspaceLocators, ) { super([...nonWorkspace, workspace]); } - public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { - const iterators: IPythonEnvsIterator[] = [this.workspace.iterEnvs(query)]; + 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))); } return combineIterators(iterators); } + + public addNewLocator(LocatorClass: ILocatorClass, isWorkspace: boolean): void { + if (isWorkspace) { + this.workspace.addNewLocator(LocatorClass); + } + if (!isWorkspace) { + this.nonWorkspace = [...this.nonWorkspace, new CustomLocator(new LocatorClass())]; + } + } } type WorkspaceLocatorFactoryResult = ILocator & Partial; type WorkspaceLocatorFactory = (root: Uri) => WorkspaceLocatorFactoryResult[]; @@ -52,12 +63,15 @@ type WatchRootsFunc = (args: WatchRootsArgs) => IDisposable; * The factories are used to produce the locators for each workspace folder. */ -export class WorkspaceLocators extends LazyResourceBasedLocator { - private readonly locators: Record, IDisposable]> = {}; +export class WorkspaceLocators extends LazyResourceBasedLocator { + 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 factory: WorkspaceLocatorFactory, + ) { super(); } @@ -69,7 +83,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]; @@ -78,7 +92,7 @@ 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(); } } // The query matches or was not location-specific. @@ -107,15 +121,13 @@ 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) => { - locators.push(loc); - if (loc.dispose !== undefined) { - disposables.push(loc as IDisposable); - } - }); + this.factory(root).forEach((loc) => { + locators.push(loc); + if (loc.dispose !== undefined) { + disposables.push(loc as IDisposable); + } }); const locator = new Locators(locators); // Cache it. @@ -133,6 +145,16 @@ export class WorkspaceLocators extends LazyResourceBasedLocat ); } + public addNewLocator(LocatorClass: ILocatorClass): void { + Object.keys(this.roots).forEach((key) => { + const root = this.roots[key]; + const newLocator = new LocatorClass(root.fsPath); + const convertedLocator: ILocator = new CustomLocator(newLocator); + const [locators] = this.locators[key]; + locators.addLocator(convertedLocator); + }); + } + private removeRoot(root: Uri): void { const key = root.toString(); const found = this.locators[key]; diff --git a/src/client/pythonEnvironments/base/watchers.ts b/src/client/pythonEnvironments/base/watchers.ts index 60bf5f7516da..a72b66d61ed9 100644 --- a/src/client/pythonEnvironments/base/watchers.ts +++ b/src/client/pythonEnvironments/base/watchers.ts @@ -30,4 +30,10 @@ export class PythonEnvsWatchers implements IPythonEnvsWatcher, IDisposable { public async dispose(): Promise { await this.disposables.dispose(); } + + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + protected addWatcher(w: IPythonEnvsWatcher) { + const disposable = w.onChanged((e) => this.watcher.fire(e)); + this.disposables.push(disposable); + } } diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index af79732b37de..49f791be59fc 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( + let locators: ILocator = new ExtensionLocators( // Here we pull the locators together. createNonWorkspaceLocators(ext), createWorkspaceLocator(ext), @@ -177,10 +177,10 @@ function watchRoots(args: WatchRootsArgs): IDisposable { }); } -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. +function createWorkspaceLocator(ext: ExtensionState): WorkspaceLocators { + const locators = new WorkspaceLocators(watchRoots, (root: vscode.Uri) => [ + new WorkspaceVirtualEnvironmentLocator(root.fsPath), + new PoetryLocator(root.fsPath), ]); ext.disposables.push(locators); return locators; From 645873aee4994587d4fe28279c80e5c373156839 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Tue, 9 Aug 2022 09:38:55 -0700 Subject: [PATCH 03/32] Add an API to add a new locator --- src/client/pythonEnvironments/api.ts | 3 + src/client/pythonEnvironments/base/locator.ts | 66 ++++++++++++++++++- .../composite/envsCollectionService.ts | 2 + .../base/locators/composite/envsReducer.ts | 2 + .../base/locators/composite/envsResolver.ts | 2 + 5 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/client/pythonEnvironments/api.ts b/src/client/pythonEnvironments/api.ts index b9c3152a0b67..847117f8f4d6 100644 --- a/src/client/pythonEnvironments/api.ts +++ b/src/client/pythonEnvironments/api.ts @@ -20,6 +20,8 @@ export type GetLocatorFunc = () => Promise; class PythonEnvironments implements IDiscoveryAPI { private locator!: IDiscoveryAPI; + public addNewLocator = this.locator.addNewLocator; + constructor( // These are factories for the sub-components the full component is composed of: private readonly getLocator: GetLocatorFunc, @@ -27,6 +29,7 @@ class PythonEnvironments implements IDiscoveryAPI { public async activate(): Promise { this.locator = await this.getLocator(); + this.addNewLocator = this.locator.addNewLocator; } public get onProgress(): Event { diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index aa6394d5241b..5dd9304fdb62 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -14,6 +14,62 @@ import { PythonEnvsWatcher, } from './watcher'; +export interface ILocatorClass { + new (root?: string): ILocatorAPI; +} + +export interface ILocatorAPI { + iterEnvs?(): IPythonEnvsIterator; + readonly onChanged?: Event; +} + +export type EnvInfo = { + envSources: EnvSource[]; + executablePath: string; + envPath?: string; +}; + +/** + * These can be used when querying for a particular env. + */ +interface EnvironmentProviderMetadata { + readonly envType: EnvType; + readonly searchLocation?: string; + readonly envSources: EnvSource[]; + readonly isRootBasedLocator: boolean; +} + +type EnvironmentMetaData = EnvironmentProviderMetadata; + +export interface LocatorEnvsChangedEvent { + /** + * Any details known about the environment which can be used for query. + */ + env?: EnvironmentMetaData; + type: EnvChangeType; +} + +export type EnvChangeType = 'add' | 'remove' | 'update'; + +export enum EnvType { + VirtualEnv = 'VirtualEnv', + Conda = 'Conda', + Unknown = 'Unknown', + Global = 'GlobalInterpreter', +} + +export enum EnvSource { + Conda = 'Conda', + Pipenv = 'PipEnv', + Poetry = 'Poetry', + VirtualEnv = 'VirtualEnv', + Venv = 'Venv', + VirtualEnvWrapper = 'VirtualEnvWrapper', + WindowsStore = 'WindowsStore', + Pyenv = 'Pyenv', + Custom = 'Custom', +} + /** * A single update to a previously provided Python env object. */ @@ -153,7 +209,9 @@ 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, + IEnvProvider { /** * Iterate over the enviroments known tos this locator. * @@ -173,6 +231,10 @@ export interface ILocator extends iterEnvs(query?: QueryForEvent): IPythonEnvsIterator; } +export interface IEnvProvider { + addNewLocator?(LocatorClass: ILocatorClass, isWorkspace: boolean): void; +} + interface IResolver { /** * Find as much info about the given Python environment as possible. @@ -203,7 +265,7 @@ export type TriggerRefreshOptions = { ifNotTriggerredAlready?: boolean; }; -export interface IDiscoveryAPI { +export interface IDiscoveryAPI extends IEnvProvider { /** * Tracks discovery progress for current list of known environments, i.e when it starts, finishes or any other relevant * stage. Note the progress for a particular query is currently not tracked or reported, this only indicates progress of diff --git a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts index eda2c25fdb70..4c4d7f6963a7 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts @@ -47,6 +47,8 @@ export class EnvsCollectionService extends PythonEnvsWatcher | undefined { const stage = options?.stage ?? ProgressReportStage.discoveryFinished; return this.progressPromises.get(stage)?.promise; diff --git a/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts b/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts index 92d81243f97b..27191f7c9f9e 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts @@ -27,6 +27,8 @@ export class PythonEnvsReducer implements ILocator { return this.parentLocator.onChanged; } + public addNewLocator = this.parentLocator.addNewLocator; + constructor(private readonly parentLocator: ILocator) {} public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { diff --git a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts index 407d2fe12172..f0538bc91ff0 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts @@ -34,6 +34,8 @@ export class PythonEnvsResolver implements IResolvingLocator { return this.parentLocator.onChanged; } + public addNewLocator = this.parentLocator.addNewLocator; + constructor( private readonly parentLocator: ILocator, private readonly environmentInfoService: IEnvironmentInfoService, From 19e12ec3b915a1495550b031ec1a9397cbf139da Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Tue, 9 Aug 2022 15:51:03 -0700 Subject: [PATCH 04/32] Create factory function --- src/client/apiTypes.ts | 9 ++++----- src/client/pythonEnvironments/base/locator.ts | 7 ++----- .../pythonEnvironments/base/locators/wrappers.ts | 12 ++++++------ 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/client/apiTypes.ts b/src/client/apiTypes.ts index ce718a144c02..f2bdaf39f52a 100644 --- a/src/client/apiTypes.ts +++ b/src/client/apiTypes.ts @@ -245,18 +245,17 @@ export interface IProposedExtensionAPI { registerEnvironmentProvider( environmentProvider: IEnvironmentProvider, metadata: EnvironmentProviderMetadata, - ): Promise; + ): Promise; // TODO: Disposable?? }; } interface IEnvironmentProvider { - locator: ILocatorClass; + // TODO: createEnv + createLocator: ILocatorFactory; getEnvironmentDetails: (env: EnvInfo) => Promise; } -export interface ILocatorClass { - new (root?: string): ILocatorAPI; -} +export type ILocatorFactory = (root?: string) => ILocatorAPI; export interface ILocatorAPI { iterEnvs?(): IPythonEnvsIterator; diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 5dd9304fdb62..d757566f9b44 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -14,10 +14,7 @@ import { PythonEnvsWatcher, } from './watcher'; -export interface ILocatorClass { - new (root?: string): ILocatorAPI; -} - +export type ILocatorFactory = (root?: string) => ILocatorAPI; export interface ILocatorAPI { iterEnvs?(): IPythonEnvsIterator; readonly onChanged?: Event; @@ -232,7 +229,7 @@ export interface ILocator { return combineIterators(iterators); } - public addNewLocator(LocatorClass: ILocatorClass, isWorkspace: boolean): void { + public addNewLocator(locatorFactory: ILocatorFactory, isWorkspace: boolean): void { if (isWorkspace) { - this.workspace.addNewLocator(LocatorClass); + this.workspace.addNewLocator(locatorFactory); } if (!isWorkspace) { - this.nonWorkspace = [...this.nonWorkspace, new CustomLocator(new LocatorClass())]; + this.nonWorkspace = [...this.nonWorkspace, new CustomLocator(locatorFactory())]; } } } @@ -145,10 +145,10 @@ export class WorkspaceLocators extends LazyResourceBasedLocator { ); } - public addNewLocator(LocatorClass: ILocatorClass): void { + public addNewLocator(locatorFactory: ILocatorFactory): void { Object.keys(this.roots).forEach((key) => { const root = this.roots[key]; - const newLocator = new LocatorClass(root.fsPath); + const newLocator = locatorFactory(root.fsPath); const convertedLocator: ILocator = new CustomLocator(newLocator); const [locators] = this.locators[key]; locators.addLocator(convertedLocator); From e6fd0e674970f39923ddc53a7495001a98c418c1 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Tue, 9 Aug 2022 16:50:45 -0700 Subject: [PATCH 05/32] Change concept of execution --- src/client/apiTypes.ts | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/client/apiTypes.ts b/src/client/apiTypes.ts index f2bdaf39f52a..bd7ddd4aeb77 100644 --- a/src/client/apiTypes.ts +++ b/src/client/apiTypes.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -103,19 +104,35 @@ export interface EnvironmentDetailsOptions { export interface EnvironmentDetails { executable: { path: string; - run: string[]; - env?: any; - shell?: boolean; - shellCommand?: Record<'cmd' | 'fish' | 'bash' | 'string', { run: string[] }>; + // run: string[]; + // env?: any; + // shell?: boolean; + // shellCommand?: Record<'cmd' | 'fish' | 'bash' | 'string', { run: string[] }>; bitness?: Architecture; cwd?: string; sysPrefix: string; }; + executionAPIs: { + // Functions would only require the arguments. The env provider can internally decide on the commands. + // Support option of whether to run as a process or VSCode terminal. + // However note we cannot pass this into the debugger at the moment, as VSCode itself handles execution. + // Gotta add support in VSCode for that, they already support that for LSP. + shellExec: Function; + shellExecObservable: Function; + exec: Function; + execObservable: Function; + }; + distributor?: { + // PEP 514 (https://www.python.org/dev/peps/pep-0514/) + name: string; // Could even be used for Pyenv. + url?: string; // 'https://www.python.org'; + }; environment?: { type: EnvType; name?: string; path: string; project?: string; // Any specific project environment is created for. + source: EnvSource[]; }; version: { major: number; @@ -136,9 +153,8 @@ export interface EnvironmentDetails { serial: number; }; }; - environmentSource: EnvSource[]; // Are the results specific to the environment (variables, working directory, etc.)? - contextSensitive: boolean; + // contextSensitive: boolean; } export interface EnvironmentsChangedParams { @@ -290,21 +306,22 @@ export interface LocatorEnvsChangedEvent { export type EnvChangeType = 'add' | 'remove' | 'update'; -export enum EnvType { +export type EnvType = KnownEnvTypes | string; + +export enum KnownEnvTypes { VirtualEnv = 'VirtualEnv', Conda = 'Conda', Unknown = 'Unknown', - Global = 'GlobalInterpreter', + Global = 'Global', } -export enum EnvSource { +export type EnvSource = KnownEnvSourceTypes | string; + +export enum KnownEnvSourceTypes { Conda = 'Conda', Pipenv = 'PipEnv', Poetry = 'Poetry', VirtualEnv = 'VirtualEnv', Venv = 'Venv', VirtualEnvWrapper = 'VirtualEnvWrapper', - WindowsStore = 'WindowsStore', - Pyenv = 'Pyenv', - Custom = 'Custom', } From 8132dca549737e179ae653d64cdc349475792227 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 10 Aug 2022 10:32:33 -0700 Subject: [PATCH 06/32] Temp --- src/client/apiTypes.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/apiTypes.ts b/src/client/apiTypes.ts index bd7ddd4aeb77..4db9b05ba391 100644 --- a/src/client/apiTypes.ts +++ b/src/client/apiTypes.ts @@ -108,8 +108,8 @@ export interface EnvironmentDetails { // env?: any; // shell?: boolean; // shellCommand?: Record<'cmd' | 'fish' | 'bash' | 'string', { run: string[] }>; + // cwd?: string; bitness?: Architecture; - cwd?: string; sysPrefix: string; }; executionAPIs: { @@ -121,6 +121,7 @@ export interface EnvironmentDetails { shellExecObservable: Function; exec: Function; execObservable: Function; + // createActivatedTerminal??? }; distributor?: { // PEP 514 (https://www.python.org/dev/peps/pep-0514/) From e77e787d025439371b964734d7e0b976f64fb2bb Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 10 Aug 2022 12:09:39 -0700 Subject: [PATCH 07/32] Contribute to apis --- src/client/apiTypes.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/client/apiTypes.ts b/src/client/apiTypes.ts index 4db9b05ba391..007bbeabcebf 100644 --- a/src/client/apiTypes.ts +++ b/src/client/apiTypes.ts @@ -3,7 +3,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { Event, Uri } from 'vscode'; +import { Event, Terminal, Uri } from 'vscode'; import { Resource } from './common/types'; import { Architecture } from './common/utils/platform'; import { IDataViewerDataProvider, IJupyterUriProvider } from './jupyter/types'; @@ -117,11 +117,11 @@ export interface EnvironmentDetails { // Support option of whether to run as a process or VSCode terminal. // However note we cannot pass this into the debugger at the moment, as VSCode itself handles execution. // Gotta add support in VSCode for that, they already support that for LSP. + // TODO: Gotta support this for upstream debugger shellExec: Function; shellExecObservable: Function; exec: Function; execObservable: Function; - // createActivatedTerminal??? }; distributor?: { // PEP 514 (https://www.python.org/dev/peps/pep-0514/) @@ -134,6 +134,22 @@ export interface EnvironmentDetails { path: string; project?: string; // Any specific project environment is created for. source: EnvSource[]; + activationAPIs?: // Contribute to this if you want Python extension to handle activation for any terminals that get created. + | { + usingEnvironmentVariableCollection: { + // TODO: Think of whether to pass in the extensions collection object here. + // Maybe we shouldn't pass it because other extension does not own the collections of the Python extensions? + setCollection(): void; + resetCollection(): void; + }; + } + | { + usingTerminal: { + // Maybe we can also pass in the terminal type here with the terminal object + activateTerminal(terminal: Terminal): Promise; + deactivateTerminal(terminal: Terminal): Promise; + }; + }; }; version: { major: number; From 0125ac4f2404d70b613350d0954ffedbecb9133b Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 10 Aug 2022 13:57:09 -0700 Subject: [PATCH 08/32] es --- src/client/apiTypes.ts | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/client/apiTypes.ts b/src/client/apiTypes.ts index 007bbeabcebf..e4dc39cf92ad 100644 --- a/src/client/apiTypes.ts +++ b/src/client/apiTypes.ts @@ -134,22 +134,7 @@ export interface EnvironmentDetails { path: string; project?: string; // Any specific project environment is created for. source: EnvSource[]; - activationAPIs?: // Contribute to this if you want Python extension to handle activation for any terminals that get created. - | { - usingEnvironmentVariableCollection: { - // TODO: Think of whether to pass in the extensions collection object here. - // Maybe we shouldn't pass it because other extension does not own the collections of the Python extensions? - setCollection(): void; - resetCollection(): void; - }; - } - | { - usingTerminal: { - // Maybe we can also pass in the terminal type here with the terminal object - activateTerminal(terminal: Terminal): Promise; - deactivateTerminal(terminal: Terminal): Promise; - }; - }; + activate: { [key: string]: string | null | undefined }; }; version: { major: number; @@ -307,7 +292,7 @@ export type EnvInfo = { interface EnvironmentProviderMetadata { readonly envType: EnvType; readonly searchLocation?: string; - readonly envSources: EnvSource[]; + readonly envSources: EnvSource[]; // Think of whether it should be an array? readonly isRootBasedLocator: boolean; } From 3404a158f1cc9257ba4664c36ea94abecdda788c Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 11 Aug 2022 17:58:18 -0700 Subject: [PATCH 09/32] Rearranging types --- src/client/apiTypes.ts | 116 ++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 47 deletions(-) diff --git a/src/client/apiTypes.ts b/src/client/apiTypes.ts index e4dc39cf92ad..0882a139688b 100644 --- a/src/client/apiTypes.ts +++ b/src/client/apiTypes.ts @@ -3,7 +3,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { Event, Terminal, Uri } from 'vscode'; +import { Disposable, Event, Uri } from 'vscode'; import { Resource } from './common/types'; import { Architecture } from './common/utils/platform'; import { IDataViewerDataProvider, IJupyterUriProvider } from './jupyter/types'; @@ -98,65 +98,62 @@ export interface IExtensionApi { export interface EnvironmentDetailsOptions { useCache: boolean; - // noShell?: boolean; // For execution without shell + /** + * Return details provided by the specific provider, throws error if provider not found. + */ + providerId?: ProviderID; } +type VersionInfo = { + major: number; + minor: number; + micro: number; + releaselevel: 'alpha' | 'beta' | 'candidate' | 'final'; + serial: number; +}; + export interface EnvironmentDetails { executable: { path: string; - // run: string[]; - // env?: any; - // shell?: boolean; - // shellCommand?: Record<'cmd' | 'fish' | 'bash' | 'string', { run: string[] }>; - // cwd?: string; + run: { + // Functions would only require the arguments. The env provider can internally decide on the commands. + // Support option of whether to run as a process or VSCode terminal. + // However note we cannot pass this into the debugger at the moment, as VSCode itself handles execution. + // Gotta add support in VSCode for that, they already support that for LSP. + // TODO: Gotta support this for upstream debugger + exec: Function; + shellExec: Function; // Only for backwards compatibility. + execObservable: Function; + /** + * Uses a VSCode terminal. + * */ + terminalExec: () => void; + /** + * Any environment variables that can be used to activate the environment, if supported. + * If not provided, Python extension itself uses the other execution APIs to calculate it. + */ + env?: { [key: string]: string | null | undefined }; + }; bitness?: Architecture; sysPrefix: string; }; - executionAPIs: { - // Functions would only require the arguments. The env provider can internally decide on the commands. - // Support option of whether to run as a process or VSCode terminal. - // However note we cannot pass this into the debugger at the moment, as VSCode itself handles execution. - // Gotta add support in VSCode for that, they already support that for LSP. - // TODO: Gotta support this for upstream debugger - shellExec: Function; - shellExecObservable: Function; - exec: Function; - execObservable: Function; - }; - distributor?: { - // PEP 514 (https://www.python.org/dev/peps/pep-0514/) - name: string; // Could even be used for Pyenv. - url?: string; // 'https://www.python.org'; - }; environment?: { type: EnvType; name?: string; path: string; project?: string; // Any specific project environment is created for. source: EnvSource[]; - activate: { [key: string]: string | null | undefined }; - }; - version: { - major: number; - minor: number; - micro: number; - releaselevel: 'alpha' | 'beta' | 'candidate' | 'final'; - serial: number; + };; + version: VersionInfo & { sysVersion?: string; - }; + };; implementation?: { // `sys.implementation` name: string; - version: { - major: number; - minor: number; - micro: number; - releaselevel: 'alpha' | 'beta' | 'candidate' | 'final'; + version: VersionInfo & { serial: number; }; }; - // Are the results specific to the environment (variables, working directory, etc.)? - // contextSensitive: boolean; } export interface EnvironmentsChangedParams { @@ -263,17 +260,24 @@ export interface IProposedExtensionAPI { registerEnvironmentProvider( environmentProvider: IEnvironmentProvider, metadata: EnvironmentProviderMetadata, - ): Promise; // TODO: Disposable?? + ): Promise; // TODO: Disposable?? // TODO: Confirm whether this should return a promise?? }; } +/** + * Provider is only expected to provide the executable key, so construct a type using `EnvironmentDetails` + * where `executable` is the only necessary key. + */ +type EnvironmentDetailsByProvider = Omit, 'executable'> & + Pick; + interface IEnvironmentProvider { - // TODO: createEnv createLocator: ILocatorFactory; - getEnvironmentDetails: (env: EnvInfo) => Promise; + getEnvironmentDetails: (env: EnvInfo) => Promise; } -export type ILocatorFactory = (root?: string) => ILocatorAPI; +type isRootBasedLocatorFactory = ((root: string) => ILocatorAPI); +export type ILocatorFactory = (() => ILocatorAPI) | isRootBasedLocatorFactory; export interface ILocatorAPI { iterEnvs?(): IPythonEnvsIterator; @@ -286,23 +290,40 @@ export type EnvInfo = { envPath?: string; }; +type ProviderID = string; + /** * These can be used when querying for a particular env. */ interface EnvironmentProviderMetadata { - readonly envType: EnvType; - readonly searchLocation?: string; - readonly envSources: EnvSource[]; // Think of whether it should be an array? + /** + * Details about the environments the locator provides. + * Useful when querying for a particular env. + */ + readonly environments?: EnvironmentMetaData; + /** + * If locator requires a root to search envs within. + */ readonly isRootBasedLocator: boolean; + /** + * An Identifier for the provider. + */ + readonly providerId: ProviderID; } -type EnvironmentMetaData = EnvironmentProviderMetadata; + interface EnvironmentMetaData { + readonly envType: EnvType; + readonly envSources: EnvSource[]; +} export interface LocatorEnvsChangedEvent { /** * Any details known about the environment which can be used for query. */ env?: EnvironmentMetaData; + /** + * Details about how the environment was modified. + **/ type: EnvChangeType; } @@ -326,4 +347,5 @@ export enum KnownEnvSourceTypes { VirtualEnv = 'VirtualEnv', Venv = 'Venv', VirtualEnvWrapper = 'VirtualEnvWrapper', + Pyenv = 'Pyenv', } From 7e6eb788336a4c4fa88e979a74a776c1c2ea3f2a Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 11 Aug 2022 20:39:40 -0700 Subject: [PATCH 10/32] Handle workspace locator factor --- src/client/apiTypes.ts | 176 +++++++++--------- src/client/pythonEnvironments/base/locator.ts | 85 +++++++-- .../base/locators/wrappers.ts | 23 ++- 3 files changed, 171 insertions(+), 113 deletions(-) diff --git a/src/client/apiTypes.ts b/src/client/apiTypes.ts index 0882a139688b..153adf5ab419 100644 --- a/src/client/apiTypes.ts +++ b/src/client/apiTypes.ts @@ -143,10 +143,10 @@ export interface EnvironmentDetails { path: string; project?: string; // Any specific project environment is created for. source: EnvSource[]; - };; + }; version: VersionInfo & { sysVersion?: string; - };; + }; implementation?: { // `sys.implementation` name: string; @@ -156,6 +156,92 @@ export interface EnvironmentDetails { }; } +/** + * Provider is only required to provide the `executable` key, rest are optional. So construct a type using + * `EnvironmentDetails` where `executable` is the only required key. + */ +type EnvironmentDetailsByProvider = Partial & Pick; + +interface IEnvironmentProvider { + createLocator: ILocatorFactory; + getEnvironmentDetails: (env: EnvInfo) => Promise; +} + +export type ILocatorFactory = INonWorkspaceLocatorFactory | IWorkspaceLocatorFactory; +export type INonWorkspaceLocatorFactory = () => ILocatorAPI; +export type IWorkspaceLocatorFactory = (root: string) => ILocatorAPI; + +export interface ILocatorAPI { + iterEnvs?(): IPythonEnvsIterator; + readonly onChanged?: Event; +} + +export type EnvInfo = { + envSources: EnvSource[]; + executablePath: string; + envPath?: string; +}; + +type ProviderID = string; + +/** + * These can be used when querying for a particular env. + */ +interface EnvironmentProviderMetadata { + /** + * Details about the environments the locator provides. + * Useful when querying for a particular env. + */ + readonly environments?: EnvironmentMetaData; + /** + * If locator requires a workspace root to search envs within. + */ + readonly isWorkspaceBasedLocator: boolean; + /** + * An Identifier for the provider. + */ + readonly providerId: ProviderID; +} + +interface EnvironmentMetaData { + readonly envType: EnvType; + readonly envSources: EnvSource[]; +} + +export interface LocatorEnvsChangedEvent { + /** + * Any details known about the environment which can be used for query. + */ + env?: EnvironmentMetaData; + /** + * Details about how the environment was modified. + * */ + type: EnvChangeType; +} + +export type EnvChangeType = 'add' | 'remove' | 'update'; + +export type EnvType = KnownEnvTypes | string; + +export enum KnownEnvTypes { + VirtualEnv = 'VirtualEnv', + Conda = 'Conda', + Unknown = 'Unknown', + Global = 'Global', +} + +export type EnvSource = KnownEnvSourceTypes | string; + +export enum KnownEnvSourceTypes { + Conda = 'Conda', + Pipenv = 'PipEnv', + Poetry = 'Poetry', + VirtualEnv = 'VirtualEnv', + Venv = 'Venv', + VirtualEnvWrapper = 'VirtualEnvWrapper', + Pyenv = 'Pyenv', +} + export interface EnvironmentsChangedParams { /** * Path to environment folder or path to interpreter that uniquely identifies an environment. @@ -263,89 +349,3 @@ export interface IProposedExtensionAPI { ): Promise; // TODO: Disposable?? // TODO: Confirm whether this should return a promise?? }; } - -/** - * Provider is only expected to provide the executable key, so construct a type using `EnvironmentDetails` - * where `executable` is the only necessary key. - */ -type EnvironmentDetailsByProvider = Omit, 'executable'> & - Pick; - -interface IEnvironmentProvider { - createLocator: ILocatorFactory; - getEnvironmentDetails: (env: EnvInfo) => Promise; -} - -type isRootBasedLocatorFactory = ((root: string) => ILocatorAPI); -export type ILocatorFactory = (() => ILocatorAPI) | isRootBasedLocatorFactory; - -export interface ILocatorAPI { - iterEnvs?(): IPythonEnvsIterator; - readonly onChanged?: Event; -} - -export type EnvInfo = { - envSources: EnvSource[]; - executablePath: string; - envPath?: string; -}; - -type ProviderID = string; - -/** - * These can be used when querying for a particular env. - */ -interface EnvironmentProviderMetadata { - /** - * Details about the environments the locator provides. - * Useful when querying for a particular env. - */ - readonly environments?: EnvironmentMetaData; - /** - * If locator requires a root to search envs within. - */ - readonly isRootBasedLocator: boolean; - /** - * An Identifier for the provider. - */ - readonly providerId: ProviderID; -} - - interface EnvironmentMetaData { - readonly envType: EnvType; - readonly envSources: EnvSource[]; -} - -export interface LocatorEnvsChangedEvent { - /** - * Any details known about the environment which can be used for query. - */ - env?: EnvironmentMetaData; - /** - * Details about how the environment was modified. - **/ - type: EnvChangeType; -} - -export type EnvChangeType = 'add' | 'remove' | 'update'; - -export type EnvType = KnownEnvTypes | string; - -export enum KnownEnvTypes { - VirtualEnv = 'VirtualEnv', - Conda = 'Conda', - Unknown = 'Unknown', - Global = 'Global', -} - -export type EnvSource = KnownEnvSourceTypes | string; - -export enum KnownEnvSourceTypes { - Conda = 'Conda', - Pipenv = 'PipEnv', - Poetry = 'Poetry', - VirtualEnv = 'VirtualEnv', - Venv = 'Venv', - VirtualEnvWrapper = 'VirtualEnvWrapper', - Pyenv = 'Pyenv', -} diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index d757566f9b44..5ca622093605 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-types */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -5,6 +6,7 @@ import { Event, Uri } from 'vscode'; import { IAsyncIterableIterator, iterEmpty } from '../../common/utils/async'; +import { Architecture } from '../../common/utils/platform'; import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from './info'; import { BasicPythonEnvsChangedEvent, @@ -14,7 +16,61 @@ import { PythonEnvsWatcher, } from './watcher'; -export type ILocatorFactory = (root?: string) => ILocatorAPI; +type VersionInfo = { + major: number; + minor: number; + micro: number; + releaselevel: 'alpha' | 'beta' | 'candidate' | 'final'; + serial: number; +}; + +export interface EnvironmentDetails { + executable: { + path: string; + run: { + // Functions would only require the arguments. The env provider can internally decide on the commands. + // Support option of whether to run as a process or VSCode terminal. + // However note we cannot pass this into the debugger at the moment, as VSCode itself handles execution. + // Gotta add support in VSCode for that, they already support that for LSP. + // TODO: Gotta support this for upstream debugger + exec: Function; + shellExec: Function; // Only for backwards compatibility. + execObservable: Function; + /** + * Uses a VSCode terminal. + * */ + terminalExec: () => void; + /** + * Any environment variables that can be used to activate the environment, if supported. + * If not provided, Python extension itself uses the other execution APIs to calculate it. + */ + env?: { [key: string]: string | null | undefined }; + }; + bitness?: Architecture; + sysPrefix: string; + }; + environment?: { + type: EnvType; + name?: string; + path: string; + project?: string; // Any specific project environment is created for. + source: EnvSource[]; + }; + version: VersionInfo & { + sysVersion?: string; + }; + implementation?: { + // `sys.implementation` + name: string; + version: VersionInfo & { + serial: number; + }; + }; +} + +type isRootBasedLocatorFactory = (root: string) => ILocatorAPI; +export type ILocatorFactory = ILocatorAPI | isRootBasedLocatorFactory; + export interface ILocatorAPI { iterEnvs?(): IPythonEnvsIterator; readonly onChanged?: Event; @@ -25,46 +81,43 @@ export type EnvInfo = { executablePath: string; envPath?: string; }; - -/** - * These can be used when querying for a particular env. - */ -interface EnvironmentProviderMetadata { +interface EnvironmentMetaData { readonly envType: EnvType; - readonly searchLocation?: string; readonly envSources: EnvSource[]; - readonly isRootBasedLocator: boolean; } -type EnvironmentMetaData = EnvironmentProviderMetadata; - export interface LocatorEnvsChangedEvent { /** * Any details known about the environment which can be used for query. */ env?: EnvironmentMetaData; + /** + * Details about how the environment was modified. + * */ type: EnvChangeType; } export type EnvChangeType = 'add' | 'remove' | 'update'; -export enum EnvType { +export type EnvType = KnownEnvTypes | string; + +export enum KnownEnvTypes { VirtualEnv = 'VirtualEnv', Conda = 'Conda', Unknown = 'Unknown', - Global = 'GlobalInterpreter', + Global = 'Global', } -export enum EnvSource { +export type EnvSource = KnownEnvSourceTypes | string; + +export enum KnownEnvSourceTypes { Conda = 'Conda', Pipenv = 'PipEnv', Poetry = 'Poetry', VirtualEnv = 'VirtualEnv', Venv = 'Venv', VirtualEnvWrapper = 'VirtualEnvWrapper', - WindowsStore = 'WindowsStore', Pyenv = 'Pyenv', - Custom = 'Custom', } /** @@ -229,7 +282,7 @@ export interface ILocator { +export class ExtensionLocators extends Locators implements IEnvProvider { constructor( // These are expected to be low-level locators (e.g. system). private nonWorkspace: ILocator[], @@ -37,12 +43,11 @@ export class ExtensionLocators extends Locators { return combineIterators(iterators); } - public addNewLocator(locatorFactory: ILocatorFactory, isWorkspace: boolean): void { - if (isWorkspace) { - this.workspace.addNewLocator(locatorFactory); - } - if (!isWorkspace) { + public addNewLocator(locatorFactory: ILocatorFactory): void { + if (IsNonWorkspaceLocatorFactory(locatorFactory)) { this.nonWorkspace = [...this.nonWorkspace, new CustomLocator(locatorFactory())]; + } else { + this.workspace.addNewLocator(locatorFactory); } } } @@ -145,7 +150,7 @@ export class WorkspaceLocators extends LazyResourceBasedLocator { ); } - public addNewLocator(locatorFactory: ILocatorFactory): void { + public addNewLocator(locatorFactory: IWorkspaceLocatorFactory): void { Object.keys(this.roots).forEach((key) => { const root = this.roots[key]; const newLocator = locatorFactory(root.fsPath); From a9637c274c1316a889844eae93be080be041d57a Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 12 Aug 2022 12:27:59 -0700 Subject: [PATCH 11/32] Attempt to contribute a resolver --- src/client/apiTypes.ts | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/src/client/apiTypes.ts b/src/client/apiTypes.ts index 153adf5ab419..f759b9cb8d58 100644 --- a/src/client/apiTypes.ts +++ b/src/client/apiTypes.ts @@ -115,25 +115,6 @@ type VersionInfo = { export interface EnvironmentDetails { executable: { path: string; - run: { - // Functions would only require the arguments. The env provider can internally decide on the commands. - // Support option of whether to run as a process or VSCode terminal. - // However note we cannot pass this into the debugger at the moment, as VSCode itself handles execution. - // Gotta add support in VSCode for that, they already support that for LSP. - // TODO: Gotta support this for upstream debugger - exec: Function; - shellExec: Function; // Only for backwards compatibility. - execObservable: Function; - /** - * Uses a VSCode terminal. - * */ - terminalExec: () => void; - /** - * Any environment variables that can be used to activate the environment, if supported. - * If not provided, Python extension itself uses the other execution APIs to calculate it. - */ - env?: { [key: string]: string | null | undefined }; - }; bitness?: Architecture; sysPrefix: string; }; @@ -163,11 +144,22 @@ export interface EnvironmentDetails { type EnvironmentDetailsByProvider = Partial & Pick; interface IEnvironmentProvider { + /** + * Factory function calling which create the locator. + */ createLocator: ILocatorFactory; - getEnvironmentDetails: (env: EnvInfo) => Promise; + /** + * Returns true if provided environment is recognized by the provider. + */ + canIdentifyEnvironment: (env: BaseEnvInfo) => Promise; + /** + * This is only called if this provider can identify the environment. + * Returns details or `undefined` if it was found if env is invalid. + */ + getEnvironmentDetails: (env: BaseEnvInfo) => Promise; } -export type ILocatorFactory = INonWorkspaceLocatorFactory | IWorkspaceLocatorFactory; +export type ILocatorFactory = IWorkspaceLocatorFactory | INonWorkspaceLocatorFactory; export type INonWorkspaceLocatorFactory = () => ILocatorAPI; export type IWorkspaceLocatorFactory = (root: string) => ILocatorAPI; @@ -176,8 +168,11 @@ export interface ILocatorAPI { readonly onChanged?: Event; } -export type EnvInfo = { +export type EnvInfo = BaseEnvInfo & { envSources: EnvSource[]; +}; + +export type BaseEnvInfo = { executablePath: string; envPath?: string; }; From bb92f602235a2f38e37bedf97ba2dd5548005b45 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 12 Aug 2022 15:06:33 -0700 Subject: [PATCH 12/32] Try to plug in resolver --- src/client/apiTypes.ts | 7 +- src/client/pythonEnvironments/base/locator.ts | 90 ++++++++++++++----- .../base/locators/wrappers.ts | 12 ++- 3 files changed, 84 insertions(+), 25 deletions(-) diff --git a/src/client/apiTypes.ts b/src/client/apiTypes.ts index f759b9cb8d58..bf8c52d57e1e 100644 --- a/src/client/apiTypes.ts +++ b/src/client/apiTypes.ts @@ -143,11 +143,16 @@ export interface EnvironmentDetails { */ type EnvironmentDetailsByProvider = Partial & Pick; -interface IEnvironmentProvider { +interface IEnvironmentProvider extends ILocatorFactoryAPI, IResolverAPI {} + +interface ILocatorFactoryAPI { /** * Factory function calling which create the locator. */ createLocator: ILocatorFactory; +} + +interface IResolverAPI { /** * Returns true if provided environment is recognized by the provider. */ diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 5ca622093605..a81931f4f220 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -16,6 +16,14 @@ import { PythonEnvsWatcher, } from './watcher'; +export interface EnvironmentDetailsOptions { + useCache: boolean; + /** + * Return details provided by the specific provider, throws error if provider not found. + */ + providerId?: ProviderID; +} + type VersionInfo = { major: number; minor: number; @@ -27,25 +35,6 @@ type VersionInfo = { export interface EnvironmentDetails { executable: { path: string; - run: { - // Functions would only require the arguments. The env provider can internally decide on the commands. - // Support option of whether to run as a process or VSCode terminal. - // However note we cannot pass this into the debugger at the moment, as VSCode itself handles execution. - // Gotta add support in VSCode for that, they already support that for LSP. - // TODO: Gotta support this for upstream debugger - exec: Function; - shellExec: Function; // Only for backwards compatibility. - execObservable: Function; - /** - * Uses a VSCode terminal. - * */ - terminalExec: () => void; - /** - * Any environment variables that can be used to activate the environment, if supported. - * If not provided, Python extension itself uses the other execution APIs to calculate it. - */ - env?: { [key: string]: string | null | undefined }; - }; bitness?: Architecture; sysPrefix: string; }; @@ -68,19 +57,76 @@ export interface EnvironmentDetails { }; } -type isRootBasedLocatorFactory = (root: string) => ILocatorAPI; -export type ILocatorFactory = ILocatorAPI | isRootBasedLocatorFactory; +/** + * Provider is only required to provide the `executable` key, rest are optional. So construct a type using + * `EnvironmentDetails` where `executable` is the only required key. + */ +type EnvironmentDetailsByProvider = Partial & Pick; + +interface IEnvironmentProvider extends ILocatorFactoryAPI, IResolverAPI {} + +interface ILocatorFactoryAPI { + /** + * Factory function calling which create the locator. + */ + createLocator: ILocatorFactory; +} + +export type ProposedDetailsAPI = (env: BaseEnvInfo) => Promise; + +export type InternalDetailsAPI = (env: BasicEnvInfo) => Promise; + +interface IResolverAPI { + /** + * Returns true if provided environment is recognized by the provider. + */ + canIdentifyEnvironment: (env: BaseEnvInfo) => Promise; + /** + * This is only called if this provider can identify the environment. + * Returns details or `undefined` if it was found if env is invalid. + */ + getEnvironmentDetails: ProposedDetailsAPI; +} + +export type ILocatorFactory = IWorkspaceLocatorFactory | INonWorkspaceLocatorFactory; +export type INonWorkspaceLocatorFactory = () => ILocatorAPI; +export type IWorkspaceLocatorFactory = (root: string) => ILocatorAPI; export interface ILocatorAPI { iterEnvs?(): IPythonEnvsIterator; readonly onChanged?: Event; } -export type EnvInfo = { +export type EnvInfo = BaseEnvInfo & { envSources: EnvSource[]; +}; + +export type BaseEnvInfo = { executablePath: string; envPath?: string; }; + +type ProviderID = string; + +/** + * These can be used when querying for a particular env. + */ +interface EnvironmentProviderMetadata { + /** + * Details about the environments the locator provides. + * Useful when querying for a particular env. + */ + readonly environments?: EnvironmentMetaData; + /** + * If locator requires a workspace root to search envs within. + */ + readonly isWorkspaceBasedLocator: boolean; + /** + * An Identifier for the provider. + */ + readonly providerId: ProviderID; +} + interface EnvironmentMetaData { readonly envType: EnvType; readonly envSources: EnvSource[]; diff --git a/src/client/pythonEnvironments/base/locators/wrappers.ts b/src/client/pythonEnvironments/base/locators/wrappers.ts index dba441b6e1a0..cc74d1818dc9 100644 --- a/src/client/pythonEnvironments/base/locators/wrappers.ts +++ b/src/client/pythonEnvironments/base/locators/wrappers.ts @@ -3,14 +3,22 @@ // eslint-disable-next-line max-classes-per-file import { Uri } from 'vscode'; -import { ILocatorFactory, INonWorkspaceLocatorFactory, IWorkspaceLocatorFactory } from '../../../apiTypes'; import { IDisposable } from '../../../common/types'; import { iterEmpty } from '../../../common/utils/async'; import { getURIFilter } from '../../../common/utils/misc'; import { Disposables } from '../../../common/utils/resourceLifecycle'; import { CustomLocator } from '../../converter'; import { PythonEnvInfo } from '../info'; -import { BasicEnvInfo, IEnvProvider, ILocator, IPythonEnvsIterator, PythonLocatorQuery } from '../locator'; +import { + BasicEnvInfo, + IEnvProvider, + ILocator, + ILocatorFactory, + INonWorkspaceLocatorFactory, + IPythonEnvsIterator, + PythonLocatorQuery, + IWorkspaceLocatorFactory, +} from '../locator'; import { combineIterators, Locators } from '../locators'; import { LazyResourceBasedLocator } from './common/resourceBasedLocator'; From 78c730bbf0a34d736155fe20cc7475edb1801df1 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 12 Aug 2022 15:29:04 -0700 Subject: [PATCH 13/32] Add converter for details API --- .../pythonEnvironments/base/info/index.ts | 7 ++- src/client/pythonEnvironments/base/locator.ts | 17 ++------ src/client/pythonEnvironments/converter.ts | 43 ++++++++++++++++++- 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index c7bcb060a221..d75cb41f6f99 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -135,13 +135,16 @@ export type PythonVersionRelease = { serial: number; }; +export type StandardVersionInfo = BasicVersionInfo & { + release?: PythonVersionRelease; +}; + /** * Version information for a Python build/installation. * * @prop sysVersion - the raw text from `sys.version` */ -export type PythonVersion = BasicVersionInfo & { - release?: PythonVersionRelease; +export type PythonVersion = StandardVersionInfo & { sysVersion?: string; }; diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index a81931f4f220..ac457163635f 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -7,7 +7,7 @@ import { Event, Uri } from 'vscode'; import { IAsyncIterableIterator, iterEmpty } from '../../common/utils/async'; import { Architecture } from '../../common/utils/platform'; -import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from './info'; +import { PythonEnvInfo, PythonEnvKind, PythonEnvSource, StandardVersionInfo } from './info'; import { BasicPythonEnvsChangedEvent, IPythonEnvsWatcher, @@ -24,14 +24,6 @@ export interface EnvironmentDetailsOptions { providerId?: ProviderID; } -type VersionInfo = { - major: number; - minor: number; - micro: number; - releaselevel: 'alpha' | 'beta' | 'candidate' | 'final'; - serial: number; -}; - export interface EnvironmentDetails { executable: { path: string; @@ -45,15 +37,13 @@ export interface EnvironmentDetails { project?: string; // Any specific project environment is created for. source: EnvSource[]; }; - version: VersionInfo & { + version: StandardVersionInfo & { sysVersion?: string; }; implementation?: { // `sys.implementation` name: string; - version: VersionInfo & { - serial: number; - }; + version: StandardVersionInfo; }; } @@ -73,7 +63,6 @@ interface ILocatorFactoryAPI { } export type ProposedDetailsAPI = (env: BaseEnvInfo) => Promise; - export type InternalDetailsAPI = (env: BasicEnvInfo) => Promise; interface IResolverAPI { diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts index 07f381606f85..270e61f349d7 100644 --- a/src/client/pythonEnvironments/converter.ts +++ b/src/client/pythonEnvironments/converter.ts @@ -5,7 +5,8 @@ import { EventEmitter, Event } from 'vscode'; import { EnvChangeType, ILocatorAPI, LocatorEnvsChangedEvent, EnvInfo } from '../apiTypes'; import { FileChangeType } from '../common/platform/fileSystemWatcher'; import { traceVerbose } from '../logging'; -import { PythonEnvKind } from './base/info'; +import { PythonEnvInfo, PythonEnvKind } from './base/info'; +import { buildEnvInfo } from './base/info/env'; import { ILocator, BasicEnvInfo, @@ -14,9 +15,49 @@ import { ProgressNotificationEvent, isProgressEvent, ProgressReportStage, + InternalDetailsAPI, + ProposedDetailsAPI, } from './base/locator'; import { PythonEnvsChangedEvent } from './base/watcher'; +export function convertIdentifierAPI(proposed: ProposedDetailsAPI): InternalDetailsAPI { + return async (env: BasicEnvInfo): Promise => { + const details = await proposed({ executablePath: env.executablePath, envPath: env.envPath }); + if (!details) { + return undefined; + } + const envInfo = buildEnvInfo({ + kind: convertKind(details.environment?.source[0] ?? PythonEnvKind.Unknown), + version: details.version, + executable: details.executable.path, + arch: details.executable.bitness, + sysPrefix: details.executable.sysPrefix, + }); + return envInfo; + }; +} + +export function convertDetailsAPI(proposed: ProposedDetailsAPI): InternalDetailsAPI { + return async (env: BasicEnvInfo): Promise => { + const details = await proposed({ executablePath: env.executablePath, envPath: env.envPath }); + if (!details) { + return undefined; + } + const envInfo = buildEnvInfo({ + kind: convertKind(details.environment?.source[0] ?? PythonEnvKind.Unknown), + version: details.version, + executable: details.executable.path, + arch: details.executable.bitness, + sysPrefix: details.executable.sysPrefix, + }); + return envInfo; + }; +} + +function convertKind(source: string): PythonEnvKind { + return (source as unknown) as PythonEnvKind; +} + /** * Converts the proposed interface into a class implementing basic interface. * ILocator ======> ILocator From 4ca72e94d78d35a3dad5ff660ccffeb4c1a4491d Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 12 Aug 2022 16:24:01 -0700 Subject: [PATCH 14/32] Modify types --- src/client/pythonEnvironments/base/info/index.ts | 4 +++- src/client/pythonEnvironments/base/locator.ts | 6 +++--- src/client/pythonEnvironments/converter.ts | 10 ++++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index d75cb41f6f99..0ed531222c83 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -26,13 +26,15 @@ export enum PythonEnvKind { OtherVirtual = 'virt-other', } +export type UniquePathType = string; + export interface EnvPathType { /** * Path to environment folder or path to interpreter that uniquely identifies an environment. * Environments lacking an interpreter are identified by environment folder paths, * whereas other envs can be identified using interpreter path. */ - path: string; + path: UniquePathType; pathType: 'envFolderPath' | 'interpreterPath'; } diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index ac457163635f..81c5bf8dc3c9 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -7,7 +7,7 @@ import { Event, Uri } from 'vscode'; import { IAsyncIterableIterator, iterEmpty } from '../../common/utils/async'; import { Architecture } from '../../common/utils/platform'; -import { PythonEnvInfo, PythonEnvKind, PythonEnvSource, StandardVersionInfo } from './info'; +import { PythonEnvInfo, PythonEnvKind, PythonEnvSource, StandardVersionInfo, UniquePathType } from './info'; import { BasicPythonEnvsChangedEvent, IPythonEnvsWatcher, @@ -69,7 +69,7 @@ interface IResolverAPI { /** * Returns true if provided environment is recognized by the provider. */ - canIdentifyEnvironment: (env: BaseEnvInfo) => Promise; + canIdentifyEnvironment: (path: UniquePathType) => Promise; /** * This is only called if this provider can identify the environment. * Returns details or `undefined` if it was found if env is invalid. @@ -105,7 +105,7 @@ interface EnvironmentProviderMetadata { * Details about the environments the locator provides. * Useful when querying for a particular env. */ - readonly environments?: EnvironmentMetaData; + readonly environments: EnvironmentMetaData; /** * If locator requires a workspace root to search envs within. */ diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts index 270e61f349d7..929623aad695 100644 --- a/src/client/pythonEnvironments/converter.ts +++ b/src/client/pythonEnvironments/converter.ts @@ -5,7 +5,7 @@ import { EventEmitter, Event } from 'vscode'; import { EnvChangeType, ILocatorAPI, LocatorEnvsChangedEvent, EnvInfo } from '../apiTypes'; import { FileChangeType } from '../common/platform/fileSystemWatcher'; import { traceVerbose } from '../logging'; -import { PythonEnvInfo, PythonEnvKind } from './base/info'; +import { PythonEnvInfo, PythonEnvKind, UniquePathType } from './base/info'; import { buildEnvInfo } from './base/info/env'; import { ILocator, @@ -17,12 +17,14 @@ import { ProgressReportStage, InternalDetailsAPI, ProposedDetailsAPI, + ProposedIdentifierAPI, + InternalIdentifierAPI, } from './base/locator'; import { PythonEnvsChangedEvent } from './base/watcher'; -export function convertIdentifierAPI(proposed: ProposedDetailsAPI): InternalDetailsAPI { - return async (env: BasicEnvInfo): Promise => { - const details = await proposed({ executablePath: env.executablePath, envPath: env.envPath }); +export function convertIdentifierAPI(proposed: ProposedIdentifierAPI): InternalIdentifierAPI { + return async (path: UniquePathType): Promise => { + const details = await proposed(path); if (!details) { return undefined; } From cb4cdaf1802c2c3544e62fab0aab76ba71aac074 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 12 Aug 2022 16:31:16 -0700 Subject: [PATCH 15/32] Add resolver converter --- src/client/pythonEnvironments/base/locator.ts | 14 +++++++++- src/client/pythonEnvironments/converter.ts | 26 ++++++------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 81c5bf8dc3c9..cf5379d45c52 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -65,7 +65,7 @@ interface ILocatorFactoryAPI { export type ProposedDetailsAPI = (env: BaseEnvInfo) => Promise; export type InternalDetailsAPI = (env: BasicEnvInfo) => Promise; -interface IResolverAPI { +export interface IResolverAPI { /** * Returns true if provided environment is recognized by the provider. */ @@ -77,6 +77,18 @@ interface IResolverAPI { getEnvironmentDetails: ProposedDetailsAPI; } +export interface IInternalResolverAPI { + /** + * Returns true if provided environment is recognized by the provider. + */ + canIdentifyEnvironment: (path: UniquePathType) => Promise; + /** + * This is only called if this provider can identify the environment. + * Returns details or `undefined` if it was found if env is invalid. + */ + getEnvironmentDetails: InternalDetailsAPI; +} + export type ILocatorFactory = IWorkspaceLocatorFactory | INonWorkspaceLocatorFactory; export type INonWorkspaceLocatorFactory = () => ILocatorAPI; export type IWorkspaceLocatorFactory = (root: string) => ILocatorAPI; diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts index 929623aad695..db8b3fdcec25 100644 --- a/src/client/pythonEnvironments/converter.ts +++ b/src/client/pythonEnvironments/converter.ts @@ -5,7 +5,7 @@ import { EventEmitter, Event } from 'vscode'; import { EnvChangeType, ILocatorAPI, LocatorEnvsChangedEvent, EnvInfo } from '../apiTypes'; import { FileChangeType } from '../common/platform/fileSystemWatcher'; import { traceVerbose } from '../logging'; -import { PythonEnvInfo, PythonEnvKind, UniquePathType } from './base/info'; +import { PythonEnvInfo, PythonEnvKind } from './base/info'; import { buildEnvInfo } from './base/info/env'; import { ILocator, @@ -17,29 +17,19 @@ import { ProgressReportStage, InternalDetailsAPI, ProposedDetailsAPI, - ProposedIdentifierAPI, - InternalIdentifierAPI, + IResolverAPI, + IInternalResolverAPI, } from './base/locator'; import { PythonEnvsChangedEvent } from './base/watcher'; -export function convertIdentifierAPI(proposed: ProposedIdentifierAPI): InternalIdentifierAPI { - return async (path: UniquePathType): Promise => { - const details = await proposed(path); - if (!details) { - return undefined; - } - const envInfo = buildEnvInfo({ - kind: convertKind(details.environment?.source[0] ?? PythonEnvKind.Unknown), - version: details.version, - executable: details.executable.path, - arch: details.executable.bitness, - sysPrefix: details.executable.sysPrefix, - }); - return envInfo; +export function convertResolverAPI(proposed: IResolverAPI): IInternalResolverAPI { + return { + canIdentifyEnvironment: proposed.canIdentifyEnvironment, + getEnvironmentDetails: convertDetailsAPI(proposed.getEnvironmentDetails), }; } -export function convertDetailsAPI(proposed: ProposedDetailsAPI): InternalDetailsAPI { +function convertDetailsAPI(proposed: ProposedDetailsAPI): InternalDetailsAPI { return async (env: BasicEnvInfo): Promise => { const details = await proposed({ executablePath: env.executablePath, envPath: env.envPath }); if (!details) { From ec6b6e0017817d3e625feeb4f34440e84c83e301 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 12 Aug 2022 16:51:34 -0700 Subject: [PATCH 16/32] Ensure resolver API isnt passed down to loators --- src/client/pythonEnvironments/api.ts | 4 ++-- src/client/pythonEnvironments/base/locator.ts | 11 ++++++++--- .../base/locators/composite/envsCollectionService.ts | 2 +- .../base/locators/composite/envsResolver.ts | 7 ++++++- .../pythonEnvironments/base/locators/wrappers.ts | 12 ++++++------ src/client/pythonEnvironments/converter.ts | 2 +- 6 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/client/pythonEnvironments/api.ts b/src/client/pythonEnvironments/api.ts index 847117f8f4d6..2a0fc5ea5f96 100644 --- a/src/client/pythonEnvironments/api.ts +++ b/src/client/pythonEnvironments/api.ts @@ -20,7 +20,7 @@ export type GetLocatorFunc = () => Promise; class PythonEnvironments implements IDiscoveryAPI { private locator!: IDiscoveryAPI; - public addNewLocator = this.locator.addNewLocator; + public addNewProvider = this.locator.addNewProvider; constructor( // These are factories for the sub-components the full component is composed of: @@ -29,7 +29,7 @@ class PythonEnvironments implements IDiscoveryAPI { public async activate(): Promise { this.locator = await this.getLocator(); - this.addNewLocator = this.locator.addNewLocator; + this.addNewProvider = this.locator.addNewProvider; } public get onProgress(): Event { diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index cf5379d45c52..db6c3f42e942 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -54,6 +54,7 @@ export interface EnvironmentDetails { type EnvironmentDetailsByProvider = Partial & Pick; interface IEnvironmentProvider extends ILocatorFactoryAPI, IResolverAPI {} +export interface IInternalEnvironmentProvider extends ILocatorFactoryAPI, IInternalResolverAPI {} interface ILocatorFactoryAPI { /** @@ -308,7 +309,7 @@ export type BasicEnvInfo = { */ export interface ILocator extends IPythonEnvsWatcher, - IEnvProvider { + ILocatorProvider { /** * Iterate over the enviroments known tos this locator. * @@ -328,10 +329,14 @@ export interface ILocator): IPythonEnvsIterator; } -export interface IEnvProvider { +export interface ILocatorProvider { addNewLocator?(locatorFactory: ILocatorFactory): void; } +export interface IEnvProvider { + addNewProvider?(environmentProvider: IInternalEnvironmentProvider): void; +} + interface IResolver { /** * Find as much info about the given Python environment as possible. @@ -342,7 +347,7 @@ interface IResolver { resolveEnv(path: string): Promise; } -export interface IResolvingLocator extends IResolver, ILocator {} +export interface IResolvingLocator extends IResolver, ILocator, IEnvProvider {} export interface GetRefreshEnvironmentsOptions { /** diff --git a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts index 4c4d7f6963a7..bf25439f2aef 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts @@ -47,7 +47,7 @@ export class EnvsCollectionService extends PythonEnvsWatcher | undefined { const stage = options?.stage ?? ProgressReportStage.discoveryFinished; diff --git a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts index f0538bc91ff0..962978a7f0ef 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts @@ -10,6 +10,7 @@ import { getEnvPath, setEnvDisplayString } from '../../info/env'; import { InterpreterInformation } from '../../info/interpreter'; import { BasicEnvInfo, + IInternalEnvironmentProvider, ILocator, IPythonEnvsIterator, IResolvingLocator, @@ -34,7 +35,11 @@ export class PythonEnvsResolver implements IResolvingLocator { return this.parentLocator.onChanged; } - public addNewLocator = this.parentLocator.addNewLocator; + public addNewProvider(provider: IInternalEnvironmentProvider): void { + if (this.parentLocator.addNewLocator) { + this.parentLocator.addNewLocator(provider.createLocator); + } + } constructor( private readonly parentLocator: ILocator, diff --git a/src/client/pythonEnvironments/base/locators/wrappers.ts b/src/client/pythonEnvironments/base/locators/wrappers.ts index cc74d1818dc9..470c8862c982 100644 --- a/src/client/pythonEnvironments/base/locators/wrappers.ts +++ b/src/client/pythonEnvironments/base/locators/wrappers.ts @@ -3,21 +3,21 @@ // eslint-disable-next-line max-classes-per-file import { Uri } from 'vscode'; +import { ILocatorFactory } from '../../../apiTypes'; import { IDisposable } from '../../../common/types'; import { iterEmpty } from '../../../common/utils/async'; import { getURIFilter } from '../../../common/utils/misc'; import { Disposables } from '../../../common/utils/resourceLifecycle'; -import { CustomLocator } from '../../converter'; +import { ConvertLocator } from '../../converter'; import { PythonEnvInfo } from '../info'; import { BasicEnvInfo, - IEnvProvider, ILocator, - ILocatorFactory, INonWorkspaceLocatorFactory, IPythonEnvsIterator, PythonLocatorQuery, IWorkspaceLocatorFactory, + ILocatorProvider, } from '../locator'; import { combineIterators, Locators } from '../locators'; import { LazyResourceBasedLocator } from './common/resourceBasedLocator'; @@ -32,7 +32,7 @@ function IsNonWorkspaceLocatorFactory( * A wrapper around all locators used by the extension. */ -export class ExtensionLocators extends Locators implements IEnvProvider { +export class ExtensionLocators extends Locators implements ILocatorProvider { constructor( // These are expected to be low-level locators (e.g. system). private nonWorkspace: ILocator[], @@ -53,7 +53,7 @@ export class ExtensionLocators extends Locators implements IEnvPro public addNewLocator(locatorFactory: ILocatorFactory): void { if (IsNonWorkspaceLocatorFactory(locatorFactory)) { - this.nonWorkspace = [...this.nonWorkspace, new CustomLocator(locatorFactory())]; + this.nonWorkspace = [...this.nonWorkspace, new ConvertLocator(locatorFactory())]; } else { this.workspace.addNewLocator(locatorFactory); } @@ -162,7 +162,7 @@ export class WorkspaceLocators extends LazyResourceBasedLocator { Object.keys(this.roots).forEach((key) => { const root = this.roots[key]; const newLocator = locatorFactory(root.fsPath); - const convertedLocator: ILocator = new CustomLocator(newLocator); + const convertedLocator: ILocator = new ConvertLocator(newLocator); const [locators] = this.locators[key]; locators.addLocator(convertedLocator); }); diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts index db8b3fdcec25..f865d2cdcf9f 100644 --- a/src/client/pythonEnvironments/converter.ts +++ b/src/client/pythonEnvironments/converter.ts @@ -54,7 +54,7 @@ function convertKind(source: string): PythonEnvKind { * Converts the proposed interface into a class implementing basic interface. * ILocator ======> ILocator */ -export class CustomLocator implements ILocator { +export class ConvertLocator implements ILocator { private readonly didChange = new EventEmitter(); private eventKeys: Record = { From ef008cda098572644540093b8c64b62c0364af9c Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 12 Aug 2022 17:13:46 -0700 Subject: [PATCH 17/32] Add provider converter --- src/client/pythonEnvironments/base/locator.ts | 12 ++++++------ .../pythonEnvironments/base/locators/wrappers.ts | 4 ++-- src/client/pythonEnvironments/converter.ts | 9 +++++++++ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index db6c3f42e942..ff9b90684b4b 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -53,7 +53,6 @@ export interface EnvironmentDetails { */ type EnvironmentDetailsByProvider = Partial & Pick; -interface IEnvironmentProvider extends ILocatorFactoryAPI, IResolverAPI {} export interface IInternalEnvironmentProvider extends ILocatorFactoryAPI, IInternalResolverAPI {} interface ILocatorFactoryAPI { @@ -94,6 +93,7 @@ export type ILocatorFactory = IWorkspaceLocatorFactory | INonWorkspaceLocatorFac export type INonWorkspaceLocatorFactory = () => ILocatorAPI; export type IWorkspaceLocatorFactory = (root: string) => ILocatorAPI; +export interface IEnvironmentProvider extends ILocatorFactoryAPI, IResolverAPI {} export interface ILocatorAPI { iterEnvs?(): IPythonEnvsIterator; readonly onChanged?: Event; @@ -309,7 +309,7 @@ export type BasicEnvInfo = { */ export interface ILocator extends IPythonEnvsWatcher, - ILocatorProvider { + ILocatorRegister { /** * Iterate over the enviroments known tos this locator. * @@ -329,11 +329,11 @@ export interface ILocator): IPythonEnvsIterator; } -export interface ILocatorProvider { +export interface ILocatorRegister { addNewLocator?(locatorFactory: ILocatorFactory): void; } -export interface IEnvProvider { +export interface IEnvProviderRegister { addNewProvider?(environmentProvider: IInternalEnvironmentProvider): void; } @@ -347,7 +347,7 @@ interface IResolver { resolveEnv(path: string): Promise; } -export interface IResolvingLocator extends IResolver, ILocator, IEnvProvider {} +export interface IResolvingLocator extends IResolver, ILocator, IEnvProviderRegister {} export interface GetRefreshEnvironmentsOptions { /** @@ -367,7 +367,7 @@ export type TriggerRefreshOptions = { ifNotTriggerredAlready?: boolean; }; -export interface IDiscoveryAPI extends IEnvProvider { +export interface IDiscoveryAPI extends IEnvProviderRegister { /** * Tracks discovery progress for current list of known environments, i.e when it starts, finishes or any other relevant * stage. Note the progress for a particular query is currently not tracked or reported, this only indicates progress of diff --git a/src/client/pythonEnvironments/base/locators/wrappers.ts b/src/client/pythonEnvironments/base/locators/wrappers.ts index 470c8862c982..ceefdf74f624 100644 --- a/src/client/pythonEnvironments/base/locators/wrappers.ts +++ b/src/client/pythonEnvironments/base/locators/wrappers.ts @@ -17,7 +17,7 @@ import { IPythonEnvsIterator, PythonLocatorQuery, IWorkspaceLocatorFactory, - ILocatorProvider, + ILocatorRegister, } from '../locator'; import { combineIterators, Locators } from '../locators'; import { LazyResourceBasedLocator } from './common/resourceBasedLocator'; @@ -32,7 +32,7 @@ function IsNonWorkspaceLocatorFactory( * A wrapper around all locators used by the extension. */ -export class ExtensionLocators extends Locators implements ILocatorProvider { +export class ExtensionLocators extends Locators implements ILocatorRegister { constructor( // These are expected to be low-level locators (e.g. system). private nonWorkspace: ILocator[], diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts index f865d2cdcf9f..eed903e8c56e 100644 --- a/src/client/pythonEnvironments/converter.ts +++ b/src/client/pythonEnvironments/converter.ts @@ -19,9 +19,18 @@ import { ProposedDetailsAPI, IResolverAPI, IInternalResolverAPI, + IInternalEnvironmentProvider, + IEnvironmentProvider, } from './base/locator'; import { PythonEnvsChangedEvent } from './base/watcher'; +export function convertProviderAPI(proposed: IEnvironmentProvider): IInternalEnvironmentProvider { + return { + createLocator: proposed.createLocator, + ...convertResolverAPI(proposed), + }; +} + export function convertResolverAPI(proposed: IResolverAPI): IInternalResolverAPI { return { canIdentifyEnvironment: proposed.canIdentifyEnvironment, From 17e50cd5205e2397af99faf7858f8955f6c8075e Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 12 Aug 2022 17:20:04 -0700 Subject: [PATCH 18/32] Add metadata converter --- src/client/pythonEnvironments/base/locator.ts | 22 ++++++++++++++++--- src/client/pythonEnvironments/converter.ts | 12 ++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index ff9b90684b4b..969601d8ce22 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -113,16 +113,32 @@ type ProviderID = string; /** * These can be used when querying for a particular env. */ -interface EnvironmentProviderMetadata { +export interface EnvironmentProviderMetadata { /** * Details about the environments the locator provides. * Useful when querying for a particular env. */ readonly environments: EnvironmentMetaData; /** - * If locator requires a workspace root to search envs within. + * An Identifier for the provider. + */ + readonly providerId: ProviderID; +} + +interface InternalEnvironmentMetaData { + readonly envType: EnvType; + readonly envKinds: PythonEnvKind[]; +} + +/** + * These can be used when querying for a particular env. + */ +export interface InternalEnvironmentProviderMetadata { + /** + * Details about the environments the locator provides. + * Useful when querying for a particular env. */ - readonly isWorkspaceBasedLocator: boolean; + readonly environments: InternalEnvironmentMetaData; /** * An Identifier for the provider. */ diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts index eed903e8c56e..feb9cf2ee547 100644 --- a/src/client/pythonEnvironments/converter.ts +++ b/src/client/pythonEnvironments/converter.ts @@ -21,6 +21,8 @@ import { IInternalResolverAPI, IInternalEnvironmentProvider, IEnvironmentProvider, + EnvironmentProviderMetadata, + InternalEnvironmentProviderMetadata, } from './base/locator'; import { PythonEnvsChangedEvent } from './base/watcher'; @@ -31,6 +33,16 @@ export function convertProviderAPI(proposed: IEnvironmentProvider): IInternalEnv }; } +export function convertProviderMetaData(proposed: EnvironmentProviderMetadata): InternalEnvironmentProviderMetadata { + return { + providerId: proposed.providerId, + environments: { + envKinds: proposed.environments.envSources.map((e) => convertKind(e)), + envType: proposed.environments.envType, + }, + }; +} + export function convertResolverAPI(proposed: IResolverAPI): IInternalResolverAPI { return { canIdentifyEnvironment: proposed.canIdentifyEnvironment, From 7fdff899df62d7b0ccf72d0b2ca3f02996d6e5c2 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 12 Aug 2022 17:39:33 -0700 Subject: [PATCH 19/32] Plug in the resolver api --- src/client/pythonEnvironments/base/locator.ts | 7 +++- .../base/locators/composite/envsResolver.ts | 31 ++++++++++----- .../base/locators/composite/resolverUtils.ts | 34 +++++++++------- .../common/environmentIdentifier.ts | 39 ++++++++++--------- src/client/pythonEnvironments/converter.ts | 2 +- 5 files changed, 67 insertions(+), 46 deletions(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 969601d8ce22..4f2f2fb6a53d 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -346,11 +346,14 @@ export interface ILocator Promise> { - const resolvers = new Map Promise>(); - Object.values(PythonEnvKind).forEach((k) => { - resolvers.set(k, resolveGloballyInstalledEnv); - }); - virtualEnvKinds.forEach((k) => { - resolvers.set(k, resolveSimpleEnv); - }); - resolvers.set(PythonEnvKind.Conda, resolveCondaEnv); - resolvers.set(PythonEnvKind.WindowsStore, resolveWindowsStoreEnv); - resolvers.set(PythonEnvKind.Pyenv, resolvePyenvEnv); - return resolvers; +const resolvers = new Map Promise>(); +Object.values(PythonEnvKind).forEach((k) => { + resolvers.set(k, resolveGloballyInstalledEnv); +}); +virtualEnvKinds.forEach((k) => { + resolvers.set(k, resolveSimpleEnv); +}); +resolvers.set(PythonEnvKind.Conda, resolveCondaEnv); +resolvers.set(PythonEnvKind.WindowsStore, resolveWindowsStoreEnv); +resolvers.set(PythonEnvKind.Pyenv, resolvePyenvEnv); + +export function registerResolver( + kind: PythonEnvKind, + resolver: (_: BasicEnvInfo, useCache?: boolean) => Promise, +): void { + resolvers.set(kind, resolver); } /** @@ -46,11 +50,13 @@ function getResolvers(): Map { +export async function resolveBasicEnv(env: BasicEnvInfo, useCache = false): Promise { const { kind, source } = env; - const resolvers = getResolvers(); const resolverForKind = resolvers.get(kind)!; const resolvedEnv = await resolverForKind(env, useCache); + if (!resolvedEnv) { + return undefined; + } resolvedEnv.searchLocation = getSearchLocation(resolvedEnv); resolvedEnv.source = uniq(resolvedEnv.source.concat(source ?? [])); if (getOSType() === OSType.Windows && resolvedEnv.source?.includes(PythonEnvSource.WindowsRegistry)) { diff --git a/src/client/pythonEnvironments/common/environmentIdentifier.ts b/src/client/pythonEnvironments/common/environmentIdentifier.ts index c183f9fd8c4f..c61eb1b1aa17 100644 --- a/src/client/pythonEnvironments/common/environmentIdentifier.ts +++ b/src/client/pythonEnvironments/common/environmentIdentifier.ts @@ -16,34 +16,35 @@ import { } from './environmentManagers/simplevirtualenvs'; import { isWindowsStoreEnvironment } from './environmentManagers/windowsStoreEnv'; -function getIdentifiers(): Map Promise> { - const notImplemented = () => Promise.resolve(false); - const defaultTrue = () => Promise.resolve(true); - const identifier: Map Promise> = new Map(); - Object.values(PythonEnvKind).forEach((k) => { - identifier.set(k, notImplemented); - }); +const identifiers: Map Promise> = new Map(); - identifier.set(PythonEnvKind.Conda, isCondaEnvironment); - identifier.set(PythonEnvKind.WindowsStore, isWindowsStoreEnvironment); - identifier.set(PythonEnvKind.Pipenv, isPipenvEnvironment); - identifier.set(PythonEnvKind.Pyenv, isPyenvEnvironment); - identifier.set(PythonEnvKind.Poetry, isPoetryEnvironment); - identifier.set(PythonEnvKind.Venv, isVenvEnvironment); - identifier.set(PythonEnvKind.VirtualEnvWrapper, isVirtualEnvWrapperEnvironment); - identifier.set(PythonEnvKind.VirtualEnv, isVirtualEnvEnvironment); - identifier.set(PythonEnvKind.Unknown, defaultTrue); - identifier.set(PythonEnvKind.OtherGlobal, isGloballyInstalledEnv); - return identifier; +export function registerIdentifier(kind: PythonEnvKind, identifier: (path: string) => Promise): void { + identifiers.set(kind, identifier); } +const notImplemented = () => Promise.resolve(false); +const defaultTrue = () => Promise.resolve(true); +Object.values(PythonEnvKind).forEach((k) => { + identifiers.set(k, notImplemented); +}); + +identifiers.set(PythonEnvKind.Conda, isCondaEnvironment); +identifiers.set(PythonEnvKind.WindowsStore, isWindowsStoreEnvironment); +identifiers.set(PythonEnvKind.Pipenv, isPipenvEnvironment); +identifiers.set(PythonEnvKind.Pyenv, isPyenvEnvironment); +identifiers.set(PythonEnvKind.Poetry, isPoetryEnvironment); +identifiers.set(PythonEnvKind.Venv, isVenvEnvironment); +identifiers.set(PythonEnvKind.VirtualEnvWrapper, isVirtualEnvWrapperEnvironment); +identifiers.set(PythonEnvKind.VirtualEnv, isVirtualEnvEnvironment); +identifiers.set(PythonEnvKind.Unknown, defaultTrue); +identifiers.set(PythonEnvKind.OtherGlobal, isGloballyInstalledEnv); + /** * Returns environment type. * @param {string} path : Absolute path to the python interpreter binary or path to environment. * @returns {PythonEnvKind} */ export async function identifyEnvironment(path: string): Promise { - const identifiers = getIdentifiers(); const prioritizedEnvTypes = getPrioritizedEnvKinds(); for (const e of prioritizedEnvTypes) { const identifier = identifiers.get(e); diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts index feb9cf2ee547..98f4da6a8fce 100644 --- a/src/client/pythonEnvironments/converter.ts +++ b/src/client/pythonEnvironments/converter.ts @@ -43,7 +43,7 @@ export function convertProviderMetaData(proposed: EnvironmentProviderMetadata): }; } -export function convertResolverAPI(proposed: IResolverAPI): IInternalResolverAPI { +function convertResolverAPI(proposed: IResolverAPI): IInternalResolverAPI { return { canIdentifyEnvironment: proposed.canIdentifyEnvironment, getEnvironmentDetails: convertDetailsAPI(proposed.getEnvironmentDetails), From c08dff2df59bccfdd9c1dc684287aea36ff0b1ae Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 12 Aug 2022 17:47:22 -0700 Subject: [PATCH 20/32] Make env types optional --- src/client/apiTypes.ts | 168 ++---------------- src/client/proposedApi.ts | 2 - src/client/pythonEnvironments/base/locator.ts | 3 +- .../base/locators/wrappers.ts | 2 +- src/client/pythonEnvironments/converter.ts | 8 +- 5 files changed, 18 insertions(+), 165 deletions(-) diff --git a/src/client/apiTypes.ts b/src/client/apiTypes.ts index bf8c52d57e1e..eec6ca48dd7a 100644 --- a/src/client/apiTypes.ts +++ b/src/client/apiTypes.ts @@ -5,12 +5,14 @@ import { Disposable, Event, Uri } from 'vscode'; import { Resource } from './common/types'; -import { Architecture } from './common/utils/platform'; import { IDataViewerDataProvider, IJupyterUriProvider } from './jupyter/types'; import { EnvPathType } from './pythonEnvironments/base/info'; import { + EnvironmentDetails, + EnvironmentDetailsOptions, + EnvironmentProviderMetadata, GetRefreshEnvironmentsOptions, - IPythonEnvsIterator, + IEnvironmentProvider, ProgressNotificationEvent, } from './pythonEnvironments/base/locator'; @@ -96,152 +98,6 @@ export interface IExtensionApi { }; } -export interface EnvironmentDetailsOptions { - useCache: boolean; - /** - * Return details provided by the specific provider, throws error if provider not found. - */ - providerId?: ProviderID; -} - -type VersionInfo = { - major: number; - minor: number; - micro: number; - releaselevel: 'alpha' | 'beta' | 'candidate' | 'final'; - serial: number; -}; - -export interface EnvironmentDetails { - executable: { - path: string; - bitness?: Architecture; - sysPrefix: string; - }; - environment?: { - type: EnvType; - name?: string; - path: string; - project?: string; // Any specific project environment is created for. - source: EnvSource[]; - }; - version: VersionInfo & { - sysVersion?: string; - }; - implementation?: { - // `sys.implementation` - name: string; - version: VersionInfo & { - serial: number; - }; - }; -} - -/** - * Provider is only required to provide the `executable` key, rest are optional. So construct a type using - * `EnvironmentDetails` where `executable` is the only required key. - */ -type EnvironmentDetailsByProvider = Partial & Pick; - -interface IEnvironmentProvider extends ILocatorFactoryAPI, IResolverAPI {} - -interface ILocatorFactoryAPI { - /** - * Factory function calling which create the locator. - */ - createLocator: ILocatorFactory; -} - -interface IResolverAPI { - /** - * Returns true if provided environment is recognized by the provider. - */ - canIdentifyEnvironment: (env: BaseEnvInfo) => Promise; - /** - * This is only called if this provider can identify the environment. - * Returns details or `undefined` if it was found if env is invalid. - */ - getEnvironmentDetails: (env: BaseEnvInfo) => Promise; -} - -export type ILocatorFactory = IWorkspaceLocatorFactory | INonWorkspaceLocatorFactory; -export type INonWorkspaceLocatorFactory = () => ILocatorAPI; -export type IWorkspaceLocatorFactory = (root: string) => ILocatorAPI; - -export interface ILocatorAPI { - iterEnvs?(): IPythonEnvsIterator; - readonly onChanged?: Event; -} - -export type EnvInfo = BaseEnvInfo & { - envSources: EnvSource[]; -}; - -export type BaseEnvInfo = { - executablePath: string; - envPath?: string; -}; - -type ProviderID = string; - -/** - * These can be used when querying for a particular env. - */ -interface EnvironmentProviderMetadata { - /** - * Details about the environments the locator provides. - * Useful when querying for a particular env. - */ - readonly environments?: EnvironmentMetaData; - /** - * If locator requires a workspace root to search envs within. - */ - readonly isWorkspaceBasedLocator: boolean; - /** - * An Identifier for the provider. - */ - readonly providerId: ProviderID; -} - -interface EnvironmentMetaData { - readonly envType: EnvType; - readonly envSources: EnvSource[]; -} - -export interface LocatorEnvsChangedEvent { - /** - * Any details known about the environment which can be used for query. - */ - env?: EnvironmentMetaData; - /** - * Details about how the environment was modified. - * */ - type: EnvChangeType; -} - -export type EnvChangeType = 'add' | 'remove' | 'update'; - -export type EnvType = KnownEnvTypes | string; - -export enum KnownEnvTypes { - VirtualEnv = 'VirtualEnv', - Conda = 'Conda', - Unknown = 'Unknown', - Global = 'Global', -} - -export type EnvSource = KnownEnvSourceTypes | string; - -export enum KnownEnvSourceTypes { - Conda = 'Conda', - Pipenv = 'PipEnv', - Poetry = 'Poetry', - VirtualEnv = 'VirtualEnv', - Venv = 'Venv', - VirtualEnvWrapper = 'VirtualEnvWrapper', - Pyenv = 'Pyenv', -} - export interface EnvironmentsChangedParams { /** * Path to environment folder or path to interpreter that uniquely identifies an environment. @@ -272,10 +128,6 @@ export interface IProposedExtensionAPI { * This event is triggered when the active environment changes. */ onDidActiveEnvironmentChanged: Event; - /** - * An event that is emitted when execution details (for a resource) change. For instance, when interpreter configuration changes. - */ - readonly onDidChangeExecutionDetails: Event; /** * Returns the path to the python binary selected by the user or as in the settings. * This is just the path to the python binary, this does not provide activation or any @@ -330,7 +182,7 @@ export interface IProposedExtensionAPI { * * clearCache : When true, this will clear the cache before environment refresh * is triggered. */ - refreshEnvironment(options?: RefreshEnvironmentsOptions): Promise; + refreshEnvironments(options?: RefreshEnvironmentsOptions): Promise; /** * Returns a promise for the ongoing refresh. Returns `undefined` if there are no active * refreshes going on. @@ -343,9 +195,11 @@ export interface IProposedExtensionAPI { */ readonly onRefreshProgress: Event; }; - registerEnvironmentProvider( - environmentProvider: IEnvironmentProvider, - metadata: EnvironmentProviderMetadata, - ): Promise; // TODO: Disposable?? // TODO: Confirm whether this should return a promise?? + // registerEnvironmentProvider( + // environmentProvider: IEnvironmentProvider, + // metadata: EnvironmentProviderMetadata, + // ): Promise; + // TODO: Confirm whether this should return a promise?? + // For eg. If we also initialize a refresh a background. }; } diff --git a/src/client/proposedApi.ts b/src/client/proposedApi.ts index 8ed307fb8872..6f7e33b73cfb 100644 --- a/src/client/proposedApi.ts +++ b/src/client/proposedApi.ts @@ -4,8 +4,6 @@ import { ConfigurationTarget, EventEmitter } from 'vscode'; import { ActiveEnvironmentChangedParams, - EnvironmentDetails, - EnvironmentDetailsOptions, EnvironmentsChangedParams, IProposedExtensionAPI, RefreshEnvironmentsOptions, diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 4f2f2fb6a53d..36cde4e56c48 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -126,7 +126,6 @@ export interface EnvironmentProviderMetadata { } interface InternalEnvironmentMetaData { - readonly envType: EnvType; readonly envKinds: PythonEnvKind[]; } @@ -147,7 +146,7 @@ export interface InternalEnvironmentProviderMetadata { interface EnvironmentMetaData { readonly envType: EnvType; - readonly envSources: EnvSource[]; + readonly envSources?: EnvSource[]; } export interface LocatorEnvsChangedEvent { diff --git a/src/client/pythonEnvironments/base/locators/wrappers.ts b/src/client/pythonEnvironments/base/locators/wrappers.ts index ceefdf74f624..5faee20cfbb3 100644 --- a/src/client/pythonEnvironments/base/locators/wrappers.ts +++ b/src/client/pythonEnvironments/base/locators/wrappers.ts @@ -3,7 +3,6 @@ // eslint-disable-next-line max-classes-per-file import { Uri } from 'vscode'; -import { ILocatorFactory } from '../../../apiTypes'; import { IDisposable } from '../../../common/types'; import { iterEmpty } from '../../../common/utils/async'; import { getURIFilter } from '../../../common/utils/misc'; @@ -18,6 +17,7 @@ import { PythonLocatorQuery, IWorkspaceLocatorFactory, ILocatorRegister, + ILocatorFactory, } from '../locator'; import { combineIterators, Locators } from '../locators'; import { LazyResourceBasedLocator } from './common/resourceBasedLocator'; diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts index 98f4da6a8fce..8cf13a0ff8ac 100644 --- a/src/client/pythonEnvironments/converter.ts +++ b/src/client/pythonEnvironments/converter.ts @@ -2,7 +2,6 @@ // Licensed under the MIT License. import { EventEmitter, Event } from 'vscode'; -import { EnvChangeType, ILocatorAPI, LocatorEnvsChangedEvent, EnvInfo } from '../apiTypes'; import { FileChangeType } from '../common/platform/fileSystemWatcher'; import { traceVerbose } from '../logging'; import { PythonEnvInfo, PythonEnvKind } from './base/info'; @@ -23,6 +22,10 @@ import { IEnvironmentProvider, EnvironmentProviderMetadata, InternalEnvironmentProviderMetadata, + EnvChangeType, + EnvInfo, + ILocatorAPI, + LocatorEnvsChangedEvent, } from './base/locator'; import { PythonEnvsChangedEvent } from './base/watcher'; @@ -37,8 +40,7 @@ export function convertProviderMetaData(proposed: EnvironmentProviderMetadata): return { providerId: proposed.providerId, environments: { - envKinds: proposed.environments.envSources.map((e) => convertKind(e)), - envType: proposed.environments.envType, + envKinds: proposed.environments.envSources?.map((e) => convertKind(e)) ?? [PythonEnvKind.Unknown], }, }; } From a6bd637a171ce5d83a5b093ecce7d3d5bbf132fb Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Tue, 16 Aug 2022 09:07:37 -0700 Subject: [PATCH 21/32] Move proposed API types to a different file --- src/client/apiTypes.ts | 117 +---------------- src/client/proposedApi.ts | 14 ++- src/client/proposedApiTypes.ts | 222 +++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 122 deletions(-) create mode 100644 src/client/proposedApiTypes.ts diff --git a/src/client/apiTypes.ts b/src/client/apiTypes.ts index eec6ca48dd7a..a6ab62fcb972 100644 --- a/src/client/apiTypes.ts +++ b/src/client/apiTypes.ts @@ -3,18 +3,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { Disposable, Event, Uri } from 'vscode'; +import { Event, Uri } from 'vscode'; import { Resource } from './common/types'; import { IDataViewerDataProvider, IJupyterUriProvider } from './jupyter/types'; -import { EnvPathType } from './pythonEnvironments/base/info'; -import { - EnvironmentDetails, - EnvironmentDetailsOptions, - EnvironmentProviderMetadata, - GetRefreshEnvironmentsOptions, - IEnvironmentProvider, - ProgressNotificationEvent, -} from './pythonEnvironments/base/locator'; /* * Do not introduce any breaking changes to this API. @@ -97,109 +88,3 @@ export interface IExtensionApi { registerRemoteServerProvider(serverProvider: IJupyterUriProvider): void; }; } - -export interface EnvironmentsChangedParams { - /** - * Path to environment folder or path to interpreter that uniquely identifies an environment. - * Environments lacking an interpreter are identified by environment folder paths, - * whereas other envs can be identified using executable path. - */ - path?: string; - type: 'add' | 'remove' | 'update' | 'clear-all'; -} - -export interface ActiveEnvironmentChangedParams { - /** - * Path to environment folder or path to interpreter that uniquely identifies an environment. - * Environments lacking an interpreter are identified by environment folder paths, - * whereas other envs can be identified using executable path. - */ - path: string; - resource?: Uri; -} - -export interface RefreshEnvironmentsOptions { - clearCache?: boolean; -} - -export interface IProposedExtensionAPI { - environment: { - /** - * This event is triggered when the active environment changes. - */ - onDidActiveEnvironmentChanged: Event; - /** - * Returns the path to the python binary selected by the user or as in the settings. - * This is just the path to the python binary, this does not provide activation or any - * other activation command. The `resource` if provided will be used to determine the - * python binary in a multi-root scenario. If resource is `undefined` then the API - * returns what ever is set for the workspace. - * @param resource : Uri of a file or workspace - */ - getActiveEnvironmentPath(resource?: Resource): Promise; - /** - * Returns details for the given interpreter. Details such as absolute interpreter path, - * version, type (conda, pyenv, etc). Metadata such as `sysPrefix` can be found under - * metadata field. - * @param path : Full path to environment folder or interpreter whose details you need. - * @param options : [optional] - * * useCache : When true, cache is checked first for any data, returns even if there - * is partial data. - */ - getEnvironmentDetails( - path: string, - options?: EnvironmentDetailsOptions, - ): Promise; - /** - * Sets the active environment path for the python extension for the resource. Configuration target - * will always be the workspace folder. - * @param path : Full path to environment folder or interpreter to set. - * @param resource : [optional] Uri of a file ro workspace to scope to a particular workspace - * folder. - */ - setActiveEnvironment(path: string, resource?: Resource): Promise; - locator: { - /** - * Returns paths to environments that uniquely identifies an environment found by the extension - * at the time of calling. This API will *not* trigger a refresh. If a refresh is going on it - * will *not* wait for the refresh to finish. This will return what is known so far. To get - * complete list `await` on promise returned by `getRefreshPromise()`. - * - * Environments lacking an interpreter are identified by environment folder paths, - * whereas other envs can be identified using executable path. - */ - getEnvironmentPaths(): Promise; - /** - * This event is triggered when the known environment list changes, like when a environment - * is found, existing environment is removed, or some details changed on an environment. - */ - onDidEnvironmentsChanged: Event; - /** - * This API will re-trigger environment discovery. Extensions can wait on the returned - * promise to get the updated environment list. If there is a refresh already going on - * then it returns the promise for that refresh. - * @param options : [optional] - * * clearCache : When true, this will clear the cache before environment refresh - * is triggered. - */ - refreshEnvironments(options?: RefreshEnvironmentsOptions): Promise; - /** - * Returns a promise for the ongoing refresh. Returns `undefined` if there are no active - * refreshes going on. - */ - getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise | undefined; - /** - * Tracks discovery progress for current list of known environments, i.e when it starts, finishes or any other relevant - * stage. Note the progress for a particular query is currently not tracked or reported, this only indicates progress of - * the entire collection. - */ - readonly onRefreshProgress: Event; - }; - // registerEnvironmentProvider( - // environmentProvider: IEnvironmentProvider, - // metadata: EnvironmentProviderMetadata, - // ): Promise; - // TODO: Confirm whether this should return a promise?? - // For eg. If we also initialize a refresh a background. - }; -} diff --git a/src/client/proposedApi.ts b/src/client/proposedApi.ts index 6f7e33b73cfb..dabcafbc9967 100644 --- a/src/client/proposedApi.ts +++ b/src/client/proposedApi.ts @@ -2,16 +2,18 @@ // Licensed under the MIT License. import { ConfigurationTarget, EventEmitter } from 'vscode'; -import { - ActiveEnvironmentChangedParams, - EnvironmentsChangedParams, - IProposedExtensionAPI, - RefreshEnvironmentsOptions, -} from './apiTypes'; import { arePathsSame } from './common/platform/fs-paths'; import { IInterpreterPathService, Resource } from './common/types'; import { IInterpreterService } from './interpreter/contracts'; import { IServiceContainer } from './ioc/types'; +import { + EnvironmentsChangedParams, + ActiveEnvironmentChangedParams, + IProposedExtensionAPI, + EnvironmentDetailsOptions, + EnvironmentDetails, + RefreshEnvironmentsOptions, +} from './proposedApiTypes'; import { PythonEnvInfo } from './pythonEnvironments/base/info'; import { getEnvPath } from './pythonEnvironments/base/info/env'; import { GetRefreshEnvironmentsOptions, IDiscoveryAPI } from './pythonEnvironments/base/locator'; diff --git a/src/client/proposedApiTypes.ts b/src/client/proposedApiTypes.ts new file mode 100644 index 000000000000..a57f310fd0d5 --- /dev/null +++ b/src/client/proposedApiTypes.ts @@ -0,0 +1,222 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Event, Uri } from 'vscode'; + +// https://github.com/microsoft/vscode-python/wiki/Proposed-Environment-APIs + +export interface IProposedExtensionAPI { + environment: { + /** + * This event is triggered when the active environment changes. + */ + onDidActiveEnvironmentChanged: Event; + /** + * Returns the path to the python binary selected by the user or as in the settings. + * This is just the path to the python binary, this does not provide activation or any + * other activation command. The `resource` if provided will be used to determine the + * python binary in a multi-root scenario. If resource is `undefined` then the API + * returns what ever is set for the workspace. + * @param resource : Uri of a file or workspace + */ + getActiveEnvironmentPath(resource?: Resource): Promise; + /** + * Returns details for the given interpreter. Details such as absolute interpreter path, + * version, type (conda, pyenv, etc). Metadata such as `sysPrefix` can be found under + * metadata field. + * @param path : Full path to environment folder or interpreter whose details you need. + * @param options : [optional] + * * useCache : When true, cache is checked first for any data, returns even if there + * is partial data. + */ + getEnvironmentDetails( + path: string, + options?: EnvironmentDetailsOptions, + ): Promise; + /** + * Sets the active environment path for the python extension for the resource. Configuration target + * will always be the workspace folder. + * @param path : Full path to environment folder or interpreter to set. + * @param resource : [optional] Uri of a file ro workspace to scope to a particular workspace + * folder. + */ + setActiveEnvironment(path: string, resource?: Resource): Promise; + locator: { + /** + * Returns paths to environments that uniquely identifies an environment found by the extension + * at the time of calling. This API will *not* trigger a refresh. If a refresh is going on it + * will *not* wait for the refresh to finish. This will return what is known so far. To get + * complete list `await` on promise returned by `getRefreshPromise()`. + * + * Environments lacking an interpreter are identified by environment folder paths, + * whereas other envs can be identified using executable path. + */ + getEnvironmentPaths(): Promise; + /** + * This event is triggered when the known environment list changes, like when a environment + * is found, existing environment is removed, or some details changed on an environment. + */ + onDidEnvironmentsChanged: Event; + /** + * This API will re-trigger environment discovery. Extensions can wait on the returned + * promise to get the updated environment list. If there is a refresh already going on + * then it returns the promise for that refresh. + * @param options : [optional] + * * clearCache : When true, this will clear the cache before environment refresh + * is triggered. + */ + refreshEnvironments(options?: RefreshEnvironmentsOptions): Promise; + /** + * Returns a promise for the ongoing refresh. Returns `undefined` if there are no active + * refreshes going on. + */ + getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise | undefined; + /** + * Tracks discovery progress for current list of known environments, i.e when it starts, finishes or any other relevant + * stage. Note the progress for a particular query is currently not tracked or reported, this only indicates progress of + * the entire collection. + */ + readonly onRefreshProgress: Event; + }; + }; +} + +export enum Architecture { + Unknown = 1, + x86 = 2, + x64 = 3, +} + +export type EnvSource = KnownEnvSourceTypes | string; + +export enum KnownEnvSourceTypes { + Conda = 'Conda', + Pipenv = 'PipEnv', + Poetry = 'Poetry', + VirtualEnv = 'VirtualEnv', + Venv = 'Venv', + VirtualEnvWrapper = 'VirtualEnvWrapper', + Pyenv = 'Pyenv', +} + +export type EnvType = KnownEnvTypes | string; + +export enum KnownEnvTypes { + VirtualEnv = 'VirtualEnv', + Conda = 'Conda', + Unknown = 'Unknown', + Global = 'Global', +} + +export type BasicVersionInfo = { + major: number; + minor: number; + micro: number; +}; + +/** + * The possible Python release levels. + */ +export enum PythonReleaseLevel { + Alpha = 'alpha', + Beta = 'beta', + Candidate = 'candidate', + Final = 'final', +} + +/** + * Release information for a Python version. + */ +export type PythonVersionRelease = { + level: PythonReleaseLevel; + serial: number; +}; + +export type StandardVersionInfo = BasicVersionInfo & { + release?: PythonVersionRelease; +}; + +export interface EnvironmentDetails { + executable: { + path: string; + bitness?: Architecture; + sysPrefix: string; + // To be added later: + // run: { + // exec: Function; + // shellExec: Function; + // execObservable: Function; + // terminalExec: () => void; + // env?: { [key: string]: string | null | undefined }; + // }; + }; + environment?: { + type: EnvType; + name?: string; + path: string; + project?: string; // Any specific project environment is created for. + source: EnvSource[]; + }; + version: StandardVersionInfo & { + sysVersion?: string; + }; + implementation?: { + // `sys.implementation` + name: string; + version: StandardVersionInfo; + }; +} + +export interface EnvironmentDetailsOptions { + useCache: boolean; +} + +export interface GetRefreshEnvironmentsOptions { + /** + * Get refresh promise which resolves once the following stage has been reached for the list of known environments. + */ + stage?: ProgressReportStage; +} + +export enum ProgressReportStage { + discoveryStarted = 'discoveryStarted', + allPathsDiscovered = 'allPathsDiscovered', + discoveryFinished = 'discoveryFinished', +} + +export type ProgressNotificationEvent = { + stage: ProgressReportStage; +}; + +export type Resource = Uri | undefined; + +/** + * Path to environment folder or path to interpreter that uniquely identifies an environment. + * Environments lacking an interpreter are identified by environment folder paths, + * whereas other envs can be identified using interpreter path. + */ +export type UniquePathType = string; + +export interface EnvPathType { + path: UniquePathType; + pathType: 'envFolderPath' | 'interpreterPath'; +} + +export interface EnvironmentsChangedParams { + path?: UniquePathType; + type: 'add' | 'remove' | 'update' | 'clear-all'; +} + +export interface ActiveEnvironmentChangedParams { + path: UniquePathType; + resource?: Uri; +} + +export interface RefreshEnvironmentsOptions { + clearCache?: boolean; +} From df3d795ab992aa0bd09931fa2e969e0f5f2775dd Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Tue, 16 Aug 2022 18:03:06 -0700 Subject: [PATCH 22/32] Change some types --- src/client/extension.ts | 3 +- src/client/pythonEnvironments/base/locator.ts | 82 +++++++++++-------- src/client/pythonEnvironments/converter.ts | 5 +- 3 files changed, 54 insertions(+), 36 deletions(-) diff --git a/src/client/extension.ts b/src/client/extension.ts index 312e99a38683..30da91a0d2ba 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -42,10 +42,11 @@ import { sendErrorTelemetry, sendStartupTelemetry } from './startupTelemetry'; import { IStartupDurations } from './types'; import { runAfterActivation } from './common/utils/runAfterActivation'; import { IInterpreterService } from './interpreter/contracts'; -import { IExtensionApi, IProposedExtensionAPI } from './apiTypes'; +import { IExtensionApi } from './apiTypes'; import { buildProposedApi } from './proposedApi'; import { WorkspaceService } from './common/application/workspace'; import { disposeAll } from './common/utils/resourceLifecycle'; +import { IProposedExtensionAPI } from './proposedApiTypes'; durations.codeLoadingTime = stopWatch.elapsedTime; diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 36cde4e56c48..0d6e67173428 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -19,9 +19,9 @@ import { export interface EnvironmentDetailsOptions { useCache: boolean; /** - * Return details provided by the specific provider, throws error if provider not found. + * Return details provided by the specific extension, throws error if provider not found. */ - providerId?: ProviderID; + extensionId?: ExtensionID; } export interface EnvironmentDetails { @@ -30,13 +30,15 @@ export interface EnvironmentDetails { bitness?: Architecture; sysPrefix: string; }; - environment?: { - type: EnvType; - name?: string; - path: string; - project?: string; // Any specific project environment is created for. - source: EnvSource[]; - }; + environment: + | { + type: EnvType; + name?: string; + path: string; + project?: string; // Any specific project environment is created for. + source: EnvSource[]; + } + | undefined; version: StandardVersionInfo & { sysVersion?: string; }; @@ -51,9 +53,13 @@ export interface EnvironmentDetails { * Provider is only required to provide the `executable` key, rest are optional. So construct a type using * `EnvironmentDetails` where `executable` is the only required key. */ -type EnvironmentDetailsByProvider = Partial & Pick; +type EnvironmentDetailsByProvider = Partial & + Pick & + Pick; -export interface IInternalEnvironmentProvider extends ILocatorFactoryAPI, IInternalResolverAPI {} +export type IInternalEnvironmentProvider = + | (ILocatorFactoryAPI & IInternalResolverAPI & IInternalIdentifierAPI) + | (ILocatorFactoryAPI & IInternalResolverAPI); interface ILocatorFactoryAPI { /** @@ -65,38 +71,52 @@ interface ILocatorFactoryAPI { export type ProposedDetailsAPI = (env: BaseEnvInfo) => Promise; export type InternalDetailsAPI = (env: BasicEnvInfo) => Promise; -export interface IResolverAPI { - /** - * Returns true if provided environment is recognized by the provider. - */ - canIdentifyEnvironment: (path: UniquePathType) => Promise; +export interface IDetailsAPI { /** - * This is only called if this provider can identify the environment. + * This is only called if the provider: + * * Can identify the environment. + * * Or can iterate out the environment. * Returns details or `undefined` if it was found if env is invalid. */ getEnvironmentDetails: ProposedDetailsAPI; } -export interface IInternalResolverAPI { +export type IResolverAPI = IDetailsAPI | (IDetailsAPI & IIdentifierAPI); + +/** + * Identifier need not be registered + */ +interface IIdentifierAPI { + /** + * Environment source the provider identifies. + */ + readonly envSource: EnvSource; /** * Returns true if provided environment is recognized by the provider. */ canIdentifyEnvironment: (path: UniquePathType) => Promise; +} + +export interface IInternalResolverAPI { + getEnvironmentDetails: InternalDetailsAPI; +} + +interface IInternalIdentifierAPI { + readonly envKind: PythonEnvKind; /** - * This is only called if this provider can identify the environment. - * Returns details or `undefined` if it was found if env is invalid. + * Returns true if provided environment is recognized by the provider. */ - getEnvironmentDetails: InternalDetailsAPI; + canIdentifyEnvironment: (path: UniquePathType) => Promise; } export type ILocatorFactory = IWorkspaceLocatorFactory | INonWorkspaceLocatorFactory; export type INonWorkspaceLocatorFactory = () => ILocatorAPI; export type IWorkspaceLocatorFactory = (root: string) => ILocatorAPI; -export interface IEnvironmentProvider extends ILocatorFactoryAPI, IResolverAPI {} +export type IEnvironmentProvider = ILocatorFactoryAPI & IResolverAPI; export interface ILocatorAPI { - iterEnvs?(): IPythonEnvsIterator; - readonly onChanged?: Event; + iterEnvs(): IPythonEnvsIterator; + readonly onChanged: Event; } export type EnvInfo = BaseEnvInfo & { @@ -108,7 +128,7 @@ export type BaseEnvInfo = { envPath?: string; }; -type ProviderID = string; +type ExtensionID = string; /** * These can be used when querying for a particular env. @@ -118,11 +138,11 @@ export interface EnvironmentProviderMetadata { * Details about the environments the locator provides. * Useful when querying for a particular env. */ - readonly environments: EnvironmentMetaData; + readonly environments?: EnvironmentMetaData; /** - * An Identifier for the provider. + * An Identifier for the extension registering the provider. */ - readonly providerId: ProviderID; + readonly extensionId: ExtensionID; } interface InternalEnvironmentMetaData { @@ -138,10 +158,7 @@ export interface InternalEnvironmentProviderMetadata { * Useful when querying for a particular env. */ readonly environments: InternalEnvironmentMetaData; - /** - * An Identifier for the provider. - */ - readonly providerId: ProviderID; + readonly extensionId: ExtensionID; } interface EnvironmentMetaData { @@ -168,7 +185,6 @@ export enum KnownEnvTypes { VirtualEnv = 'VirtualEnv', Conda = 'Conda', Unknown = 'Unknown', - Global = 'Global', } export type EnvSource = KnownEnvSourceTypes | string; diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts index 8cf13a0ff8ac..99f403858db8 100644 --- a/src/client/pythonEnvironments/converter.ts +++ b/src/client/pythonEnvironments/converter.ts @@ -38,15 +38,16 @@ export function convertProviderAPI(proposed: IEnvironmentProvider): IInternalEnv export function convertProviderMetaData(proposed: EnvironmentProviderMetadata): InternalEnvironmentProviderMetadata { return { - providerId: proposed.providerId, + extensionId: proposed.extensionId, environments: { - envKinds: proposed.environments.envSources?.map((e) => convertKind(e)) ?? [PythonEnvKind.Unknown], + envKinds: proposed.environments?.envSources?.map((e) => convertKind(e)) ?? [PythonEnvKind.Unknown], }, }; } function convertResolverAPI(proposed: IResolverAPI): IInternalResolverAPI { return { + envKind: proposed.envSource ? convertKind(proposed.envSource) : undefined, canIdentifyEnvironment: proposed.canIdentifyEnvironment, getEnvironmentDetails: convertDetailsAPI(proposed.getEnvironmentDetails), }; From 6fb2a5c66d6cd139fe01a0ba32ba6d0defb3750b Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 18 Aug 2022 16:14:46 -0700 Subject: [PATCH 23/32] Add support for multiple environment identifiers --- src/client/pythonEnvironments/base/locator.ts | 21 +++++----- .../base/locators/composite/envsResolver.ts | 8 +++- .../common/environmentIdentifier.ts | 41 +++++++++++++------ src/client/pythonEnvironments/converter.ts | 8 +++- 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 0d6e67173428..9177efe211d1 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -70,22 +70,22 @@ interface ILocatorFactoryAPI { export type ProposedDetailsAPI = (env: BaseEnvInfo) => Promise; export type InternalDetailsAPI = (env: BasicEnvInfo) => Promise; - -export interface IDetailsAPI { +export interface IResolverAPI { + /** + * Carries API to check if an environment can be recognized by the provider. Providers + * which returns details about an {@link EnvSource} are expected to + * provide this. + */ + readonly sourceIdentifier: IIdentifierAPI | undefined; /** - * This is only called if the provider: - * * Can identify the environment. - * * Or can iterate out the environment. * Returns details or `undefined` if it was found if env is invalid. + * This is only called if: + * * The provider can identify the environment. + * * To get more details out of an environment already iterated by the provider. */ getEnvironmentDetails: ProposedDetailsAPI; } -export type IResolverAPI = IDetailsAPI | (IDetailsAPI & IIdentifierAPI); - -/** - * Identifier need not be registered - */ interface IIdentifierAPI { /** * Environment source the provider identifies. @@ -98,6 +98,7 @@ interface IIdentifierAPI { } export interface IInternalResolverAPI { + readonly kindIdentifier: IInternalIdentifierAPI | undefined; getEnvironmentDetails: InternalDetailsAPI; } diff --git a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts index 5f899b807fd1..4730a8755e38 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts @@ -40,7 +40,13 @@ export class PythonEnvsResolver implements IResolvingLocator { if (this.parentLocator.addNewLocator) { this.parentLocator.addNewLocator(provider.createLocator, metadata); } - registerIdentifier(metadata.environments.envKinds[0], provider.canIdentifyEnvironment); + if (provider.kindIdentifier) { + registerIdentifier( + provider.kindIdentifier.envKind, + provider.kindIdentifier.canIdentifyEnvironment, + metadata.extensionId, + ); + } registerResolver(metadata.environments.envKinds[0], provider.getEnvironmentDetails); } diff --git a/src/client/pythonEnvironments/common/environmentIdentifier.ts b/src/client/pythonEnvironments/common/environmentIdentifier.ts index c61eb1b1aa17..2e039710e47b 100644 --- a/src/client/pythonEnvironments/common/environmentIdentifier.ts +++ b/src/client/pythonEnvironments/common/environmentIdentifier.ts @@ -16,10 +16,20 @@ import { } from './environmentManagers/simplevirtualenvs'; import { isWindowsStoreEnvironment } from './environmentManagers/windowsStoreEnv'; -const identifiers: Map Promise> = new Map(); +type IdentifierType = (path: string) => Promise; +type IdentifiersType = { identifier: IdentifierType; extensionId?: string }; +const identifiers: Map = new Map(); -export function registerIdentifier(kind: PythonEnvKind, identifier: (path: string) => Promise): void { - identifiers.set(kind, identifier); +export function registerIdentifier(kind: PythonEnvKind, identifier: IdentifierType, extensionId: string): void { + const identifiersForKind = identifiers.get(kind); + if (!identifiersForKind) { + identifiers.set(kind, identifier); + } else if (Array.isArray(identifiersForKind)) { + identifiersForKind.push({ identifier, extensionId }); + identifiers.set(kind, identifiersForKind); + } else { + identifiers.set(kind, [{ identifier, extensionId }, { identifier: identifiersForKind }]); + } } const notImplemented = () => Promise.resolve(false); @@ -47,15 +57,22 @@ identifiers.set(PythonEnvKind.OtherGlobal, isGloballyInstalledEnv); export async function identifyEnvironment(path: string): Promise { const prioritizedEnvTypes = getPrioritizedEnvKinds(); for (const e of prioritizedEnvTypes) { - const identifier = identifiers.get(e); - if ( - identifier && - (await identifier(path).catch((ex) => { - traceWarn(`Identifier for ${e} failed to identify ${path}`, ex); - return false; - })) - ) { - return e; + const value = identifiers.get(e); + if (value) { + let identifier: IdentifierType; + if (Array.isArray(value)) { + identifier = value[0].identifier; + } else { + identifier = value; + } + if ( + await identifier(path).catch((ex) => { + traceWarn(`Identifier for ${e} failed to identify ${path}`, ex); + return false; + }) + ) { + return e; + } } } return PythonEnvKind.Unknown; diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts index 99f403858db8..c9b677020b57 100644 --- a/src/client/pythonEnvironments/converter.ts +++ b/src/client/pythonEnvironments/converter.ts @@ -47,8 +47,12 @@ export function convertProviderMetaData(proposed: EnvironmentProviderMetadata): function convertResolverAPI(proposed: IResolverAPI): IInternalResolverAPI { return { - envKind: proposed.envSource ? convertKind(proposed.envSource) : undefined, - canIdentifyEnvironment: proposed.canIdentifyEnvironment, + kindIdentifier: proposed.sourceIdentifier + ? { + envKind: convertKind(proposed.sourceIdentifier.envSource), + canIdentifyEnvironment: proposed.sourceIdentifier.canIdentifyEnvironment, + } + : undefined, getEnvironmentDetails: convertDetailsAPI(proposed.getEnvironmentDetails), }; } From e7a11ba229765a54bbf524e55b2d05c243ae6665 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 18 Aug 2022 17:45:43 -0700 Subject: [PATCH 24/32] Add support for multiple resolvers --- .../base/locators/composite/resolverUtils.ts | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index a8c9d737365f..ee28436285bf 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -27,7 +27,9 @@ import { BasicEnvInfo } from '../../locator'; import { parseVersionFromExecutable } from '../../info/executable'; import { traceError, traceWarn } from '../../../../logging'; -const resolvers = new Map Promise>(); +type ResolverType = (_: BasicEnvInfo, useCache?: boolean) => Promise; +type ResolversType = { resolver: ResolverType; extensionId?: string }[]; +const resolvers = new Map(); Object.values(PythonEnvKind).forEach((k) => { resolvers.set(k, resolveGloballyInstalledEnv); }); @@ -41,7 +43,17 @@ resolvers.set(PythonEnvKind.Pyenv, resolvePyenvEnv); export function registerResolver( kind: PythonEnvKind, resolver: (_: BasicEnvInfo, useCache?: boolean) => Promise, + extensionId: string, ): void { + const resolversForKind = resolvers.get(kind); + if (!resolversForKind) { + resolvers.set(kind, resolver); + } else if (Array.isArray(resolversForKind)) { + resolversForKind.push({ resolver, extensionId }); + resolvers.set(kind, resolversForKind); + } else { + resolvers.set(kind, [{ resolver, extensionId }, { resolver: resolversForKind }]); + } resolvers.set(kind, resolver); } @@ -50,9 +62,27 @@ export function registerResolver( * executable and returns it. Notice `undefined` is never returned, so environment * returned could still be invalid. */ -export async function resolveBasicEnv(env: BasicEnvInfo, useCache = false): Promise { +export async function resolveBasicEnv( + env: BasicEnvInfo, + useCache = false, + extensionId?: string, +): Promise { const { kind, source } = env; - const resolverForKind = resolvers.get(kind)!; + const value = resolvers.get(kind); + if (!value) { + traceError('No resolver found for kind:', kind); + return undefined; + } + let resolverForKind: ResolverType; + if (Array.isArray(value)) { + const resolver = extensionId ? value.find((v) => v.extensionId === extensionId)?.resolver : value[0].resolver; + if (!resolver) { + return undefined; + } + resolverForKind = resolver; + } else { + resolverForKind = value; + } const resolvedEnv = await resolverForKind(env, useCache); if (!resolvedEnv) { return undefined; From 452ec5ff8162164e8fcd214c2f5088e2a113ec2b Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 18 Aug 2022 17:58:46 -0700 Subject: [PATCH 25/32] Register properly --- src/client/pythonEnvironments/base/locator.ts | 36 +++++-------------- .../base/locators/composite/envsResolver.ts | 17 ++++++--- src/client/pythonEnvironments/converter.ts | 8 ++--- 3 files changed, 23 insertions(+), 38 deletions(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 9177efe211d1..c2b450d524be 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -57,9 +57,7 @@ type EnvironmentDetailsByProvider = Partial & Pick & Pick; -export type IInternalEnvironmentProvider = - | (ILocatorFactoryAPI & IInternalResolverAPI & IInternalIdentifierAPI) - | (ILocatorFactoryAPI & IInternalResolverAPI); +export type IInternalEnvironmentProvider = ILocatorFactoryAPI & IInternalResolverAPI; interface ILocatorFactoryAPI { /** @@ -72,11 +70,13 @@ export type ProposedDetailsAPI = (env: BaseEnvInfo) => Promise Promise; export interface IResolverAPI { /** - * Carries API to check if an environment can be recognized by the provider. Providers - * which returns details about an {@link EnvSource} are expected to - * provide this. + * Environment source the provider identifies/resolves. */ - readonly sourceIdentifier: IIdentifierAPI | undefined; + readonly envSource: EnvSource | undefined; + /** + * Returns true if provided environment is recognized by the provider. + */ + canIdentifyEnvironment: (path: UniquePathType) => Promise; /** * Returns details or `undefined` if it was found if env is invalid. * This is only called if: @@ -86,28 +86,10 @@ export interface IResolverAPI { getEnvironmentDetails: ProposedDetailsAPI; } -interface IIdentifierAPI { - /** - * Environment source the provider identifies. - */ - readonly envSource: EnvSource; - /** - * Returns true if provided environment is recognized by the provider. - */ - canIdentifyEnvironment: (path: UniquePathType) => Promise; -} - export interface IInternalResolverAPI { - readonly kindIdentifier: IInternalIdentifierAPI | undefined; - getEnvironmentDetails: InternalDetailsAPI; -} - -interface IInternalIdentifierAPI { - readonly envKind: PythonEnvKind; - /** - * Returns true if provided environment is recognized by the provider. - */ + readonly envKind: PythonEnvKind | undefined; canIdentifyEnvironment: (path: UniquePathType) => Promise; + getEnvironmentDetails: InternalDetailsAPI; } export type ILocatorFactory = IWorkspaceLocatorFactory | INonWorkspaceLocatorFactory; diff --git a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts index 4730a8755e38..0c10b9482c1e 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts @@ -5,7 +5,7 @@ import { cloneDeep } from 'lodash'; import { Event, EventEmitter } from 'vscode'; import { identifyEnvironment, registerIdentifier } from '../../../common/environmentIdentifier'; import { IEnvironmentInfoService } from '../../info/environmentInfoService'; -import { PythonEnvInfo } from '../../info'; +import { PythonEnvInfo, PythonEnvKind } from '../../info'; import { getEnvPath, setEnvDisplayString } from '../../info/env'; import { InterpreterInformation } from '../../info/interpreter'; import { @@ -40,14 +40,21 @@ export class PythonEnvsResolver implements IResolvingLocator { if (this.parentLocator.addNewLocator) { this.parentLocator.addNewLocator(provider.createLocator, metadata); } - if (provider.kindIdentifier) { + if (provider.envKind) { + registerIdentifier(provider.envKind, provider.canIdentifyEnvironment, metadata.extensionId); + registerResolver(provider.envKind, provider.getEnvironmentDetails, metadata.extensionId); + } else { registerIdentifier( - provider.kindIdentifier.envKind, - provider.kindIdentifier.canIdentifyEnvironment, + metadata.extensionId as PythonEnvKind, + provider.canIdentifyEnvironment, + metadata.extensionId, + ); + registerResolver( + metadata.extensionId as PythonEnvKind, + provider.getEnvironmentDetails, metadata.extensionId, ); } - registerResolver(metadata.environments.envKinds[0], provider.getEnvironmentDetails); } constructor( diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts index c9b677020b57..99f403858db8 100644 --- a/src/client/pythonEnvironments/converter.ts +++ b/src/client/pythonEnvironments/converter.ts @@ -47,12 +47,8 @@ export function convertProviderMetaData(proposed: EnvironmentProviderMetadata): function convertResolverAPI(proposed: IResolverAPI): IInternalResolverAPI { return { - kindIdentifier: proposed.sourceIdentifier - ? { - envKind: convertKind(proposed.sourceIdentifier.envSource), - canIdentifyEnvironment: proposed.sourceIdentifier.canIdentifyEnvironment, - } - : undefined, + envKind: proposed.envSource ? convertKind(proposed.envSource) : undefined, + canIdentifyEnvironment: proposed.canIdentifyEnvironment, getEnvironmentDetails: convertDetailsAPI(proposed.getEnvironmentDetails), }; } From 727f03b07d334e9209e01dd04b9472b81b59f1ea Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 18 Aug 2022 18:07:42 -0700 Subject: [PATCH 26/32] Also emit extensionId from downstream locators --- src/client/pythonEnvironments/base/locator.ts | 1 + src/client/pythonEnvironments/converter.ts | 112 ++++++++++-------- 2 files changed, 61 insertions(+), 52 deletions(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index c2b450d524be..25d1d35041be 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -305,6 +305,7 @@ export type BasicEnvInfo = { executablePath: string; source?: PythonEnvSource[]; envPath?: string; + extensionId?: string; }; /** diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts index 99f403858db8..12ef07b1ff56 100644 --- a/src/client/pythonEnvironments/converter.ts +++ b/src/client/pythonEnvironments/converter.ts @@ -91,7 +91,10 @@ export class ConvertLocator implements ILocator { return this.didChange.event; } - constructor(private readonly parentLocator: ILocatorAPI) { + constructor( + private readonly parentLocator: ILocatorAPI, + private readonly metadata: InternalEnvironmentProviderMetadata, + ) { if (parentLocator.onChanged) { parentLocator.onChanged((e: LocatorEnvsChangedEvent) => { const event: PythonEnvsChangedEvent = { type: this.eventKeys[`${e.type}`] }; @@ -104,61 +107,71 @@ export class ConvertLocator implements ILocator { public iterEnvs(): IPythonEnvsIterator { const didUpdate = new EventEmitter | ProgressNotificationEvent>(); const incomingIterator = this.parentLocator.iterEnvs!(); - const iterator = iterEnvsIterator(incomingIterator, didUpdate); + const iterator = this.iterEnvsIterator(incomingIterator, didUpdate); iterator.onUpdated = didUpdate.event; return iterator; } -} -async function* iterEnvsIterator( - iterator: IPythonEnvsIterator, - didUpdate: EventEmitter | ProgressNotificationEvent>, -): IPythonEnvsIterator { - const state = { - done: false, - pending: 0, - }; - const seen: BasicEnvInfo[] = []; - - if (iterator.onUpdated !== undefined) { - const listener = iterator.onUpdated((event) => { - state.pending += 1; - if (isProgressEvent(event)) { - if (event.stage === ProgressReportStage.discoveryFinished) { - state.done = true; - listener.dispose(); + private async *iterEnvsIterator( + iterator: IPythonEnvsIterator, + didUpdate: EventEmitter | ProgressNotificationEvent>, + ): IPythonEnvsIterator { + const state = { + done: false, + pending: 0, + }; + const seen: BasicEnvInfo[] = []; + + if (iterator.onUpdated !== undefined) { + const listener = iterator.onUpdated((event) => { + state.pending += 1; + if (isProgressEvent(event)) { + if (event.stage === ProgressReportStage.discoveryFinished) { + state.done = true; + listener.dispose(); + } else { + didUpdate.fire(event); + } + } else if (event.update === undefined) { + throw new Error( + 'Unsupported behavior: `undefined` environment updates are not supported from downstream locators in reducer', + ); + } else if (seen[event.index] !== undefined) { + const oldEnv = seen[event.index]; + seen[event.index] = this.convertToBasicEnv(event.update); + didUpdate.fire({ index: event.index, old: oldEnv, update: this.convertToBasicEnv(event.update) }); } else { - didUpdate.fire(event); + // This implies a problem in a downstream locator + traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`); } - } else if (event.update === undefined) { - throw new Error( - 'Unsupported behavior: `undefined` environment updates are not supported from downstream locators in reducer', - ); - } else if (seen[event.index] !== undefined) { - const oldEnv = seen[event.index]; - seen[event.index] = convertToBasicEnv(event.update); - didUpdate.fire({ index: event.index, old: oldEnv, update: convertToBasicEnv(event.update) }); - } else { - // This implies a problem in a downstream locator - traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`); - } - state.pending -= 1; + state.pending -= 1; + checkIfFinishedAndNotify(state, didUpdate); + }); + } else { + didUpdate.fire({ stage: ProgressReportStage.discoveryStarted }); + } + + let result = await iterator.next(); + while (!result.done) { + const currEnv = this.convertToBasicEnv(result.value); + yield currEnv; + seen.push(currEnv); + result = await iterator.next(); + } + if (iterator.onUpdated === undefined) { + state.done = true; checkIfFinishedAndNotify(state, didUpdate); - }); - } else { - didUpdate.fire({ stage: ProgressReportStage.discoveryStarted }); + } } - let result = await iterator.next(); - while (!result.done) { - const currEnv = convertToBasicEnv(result.value); - yield currEnv; - seen.push(currEnv); - result = await iterator.next(); - } - if (iterator.onUpdated === undefined) { - state.done = true; - checkIfFinishedAndNotify(state, didUpdate); + private convertToBasicEnv(env: EnvInfo): BasicEnvInfo { + // TODO: Support multiple kinds + return { + executablePath: env.executablePath, + envPath: env.envPath, + kind: convertKind(env.envSources[0]), + extensionId: this.metadata.extensionId, + }; } } @@ -176,8 +189,3 @@ function checkIfFinishedAndNotify( didUpdate.dispose(); } } - -function convertToBasicEnv(env: EnvInfo): BasicEnvInfo { - // TODO: Use kind converter - return { executablePath: env.executablePath, envPath: env.envPath, kind: PythonEnvKind.Unknown }; -} From 78e09360a96d98f270b30ce5c93a0c40eaffac78 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 19 Aug 2022 11:41:29 -0700 Subject: [PATCH 27/32] Add support for multiple kinds --- .../pythonEnvironments/base/info/env.ts | 14 +++++----- .../pythonEnvironments/base/info/envKind.ts | 24 +++++++++++++++++ .../base/info/environmentInfoService.ts | 5 ++-- .../pythonEnvironments/base/info/index.ts | 2 +- src/client/pythonEnvironments/base/locator.ts | 10 +++---- .../base/locators/composite/envsReducer.ts | 24 ++++++++++------- .../base/locators/composite/envsResolver.ts | 2 +- .../base/locators/composite/resolverUtils.ts | 27 +++++++++---------- .../common/environmentIdentifier.ts | 4 +-- 9 files changed, 69 insertions(+), 43 deletions(-) diff --git a/src/client/pythonEnvironments/base/info/env.ts b/src/client/pythonEnvironments/base/info/env.ts index a9aea9d42a97..354befbfa12d 100644 --- a/src/client/pythonEnvironments/base/info/env.ts +++ b/src/client/pythonEnvironments/base/info/env.ts @@ -28,7 +28,7 @@ import { BasicEnvInfo } from '../locator'; * @param init - if provided, these values are applied to the new object */ export function buildEnvInfo(init?: { - kind?: PythonEnvKind; + kind?: PythonEnvKind[]; executable?: string; name?: string; location?: string; @@ -44,7 +44,7 @@ export function buildEnvInfo(init?: { const env: PythonEnvInfo = { name: init?.name ?? '', location: '', - kind: PythonEnvKind.Unknown, + kind: [PythonEnvKind.Unknown], executable: { filename: '', sysPrefix: init?.sysPrefix ?? '', @@ -83,7 +83,7 @@ export function buildEnvInfo(init?: { export function copyEnvInfo( env: PythonEnvInfo, updates?: { - kind?: PythonEnvKind; + kind?: PythonEnvKind[]; }, ): PythonEnvInfo { // We don't care whether or not extra/hidden properties @@ -98,7 +98,7 @@ export function copyEnvInfo( function updateEnv( env: PythonEnvInfo, updates: { - kind?: PythonEnvKind; + kind?: PythonEnvKind[]; executable?: string; location?: string; version?: PythonVersion; @@ -135,8 +135,8 @@ export function setEnvDisplayString(env: PythonEnvInfo): void { function buildEnvDisplayString(env: PythonEnvInfo, getAllDetails = false): string { // main parts - const shouldDisplayKind = getAllDetails || env.searchLocation || globallyInstalledEnvKinds.includes(env.kind); - const shouldDisplayArch = !virtualEnvKinds.includes(env.kind); + const shouldDisplayKind = getAllDetails || env.searchLocation || globallyInstalledEnvKinds.includes(env.kind[0]); + const shouldDisplayArch = !virtualEnvKinds.includes(env.kind[0]); const displayNameParts: string[] = ['Python']; if (env.version && !isVersionEmpty(env.version)) { displayNameParts.push(getVersionDisplayString(env.version)); @@ -156,7 +156,7 @@ function buildEnvDisplayString(env: PythonEnvInfo, getAllDetails = false): strin envSuffixParts.push(`'${env.name}'`); } if (shouldDisplayKind) { - const kindName = getKindDisplayName(env.kind); + const kindName = getKindDisplayName(env.kind[0]); if (kindName !== '') { envSuffixParts.push(kindName); } diff --git a/src/client/pythonEnvironments/base/info/envKind.ts b/src/client/pythonEnvironments/base/info/envKind.ts index 63f2b6afe483..f1c83c7d1552 100644 --- a/src/client/pythonEnvironments/base/info/envKind.ts +++ b/src/client/pythonEnvironments/base/info/envKind.ts @@ -70,3 +70,27 @@ export function getPrioritizedEnvKinds(): PythonEnvKind[] { PythonEnvKind.Unknown, ]; } + +const envKindByPriority = getPrioritizedEnvKinds(); + +export const sortKindFunction = (a: PythonEnvKind, b: PythonEnvKind): number => + envKindByPriority.indexOf(a) - envKindByPriority.indexOf(b); + +/** + * Sorts which extension id should be preferred for resolving, identification, reducing etc. + * + * Note `extensionId` property is considered `undefined` if env is discovered by Python extension. + */ +export const sortExtensionSource = (extensionId1: string | undefined, extensionId2: string | undefined): number => { + // If another extension provides an env, prefer that over what Python extension provides. + if (extensionId1) { + if (extensionId2) { + return sortExtensionSource(extensionId1, extensionId2); + } + return -1; + } + if (extensionId2) { + return 1; + } + return 0; +}; diff --git a/src/client/pythonEnvironments/base/info/environmentInfoService.ts b/src/client/pythonEnvironments/base/info/environmentInfoService.ts index c6d4308bee89..75a92fac6c57 100644 --- a/src/client/pythonEnvironments/base/info/environmentInfoService.ts +++ b/src/client/pythonEnvironments/base/info/environmentInfoService.ts @@ -102,7 +102,7 @@ class EnvironmentInfoService implements IEnvironmentInfoService { env: PythonEnvInfo, priority?: EnvironmentInfoServiceQueuePriority, ): Promise { - if (env.kind === PythonEnvKind.Conda && env.executable.filename === 'python') { + if (env.kind.includes(PythonEnvKind.Conda) && env.executable.filename === 'python') { const emptyInterpreterInfo: InterpreterInformation = { arch: Architecture.Unknown, executable: { @@ -131,7 +131,8 @@ class EnvironmentInfoService implements IEnvironmentInfoService { if (r === undefined) { // Even though env kind is not conda, it can still be a conda environment // as complete env info may not be available at this time. - const isCondaEnv = env.kind === PythonEnvKind.Conda || (await isCondaEnvironment(env.executable.filename)); + const isCondaEnv = + env.kind.includes(PythonEnvKind.Conda) || (await isCondaEnvironment(env.executable.filename)); if (isCondaEnv) { traceInfo( `Validating ${env.executable.filename} normally failed with error, falling back to using conda run: (${reason})`, diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index 0ed531222c83..83b28d262093 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -106,7 +106,7 @@ export enum PythonEnvSource { */ type PythonEnvBaseInfo = { id?: string; - kind: PythonEnvKind; + kind: PythonEnvKind[]; executable: PythonExecutableInfo; // One of (name, location) must be non-empty. name: string; diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 25d1d35041be..821fb6cdd695 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -18,10 +18,6 @@ import { export interface EnvironmentDetailsOptions { useCache: boolean; - /** - * Return details provided by the specific extension, throws error if provider not found. - */ - extensionId?: ExtensionID; } export interface EnvironmentDetails { @@ -74,7 +70,7 @@ export interface IResolverAPI { */ readonly envSource: EnvSource | undefined; /** - * Returns true if provided environment is recognized by the provider. + * Returns true if provided environment comes from the specified env source. */ canIdentifyEnvironment: (path: UniquePathType) => Promise; /** @@ -301,11 +297,11 @@ export type PythonLocatorQuery = BasicPythonLocatorQuery & { type QueryForEvent = E extends PythonEnvsChangedEvent ? PythonLocatorQuery : BasicPythonLocatorQuery; export type BasicEnvInfo = { - kind: PythonEnvKind; + kind: PythonEnvKind[]; executablePath: string; source?: PythonEnvSource[]; envPath?: string; - extensionId?: string; + extensionId?: ExtensionID; }; /** diff --git a/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts b/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts index 27191f7c9f9e..5fcc275b095b 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { cloneDeep, isEqual, uniq } from 'lodash'; +import { cloneDeep, isEqual, union } from 'lodash'; import { Event, EventEmitter } from 'vscode'; import { traceVerbose } from '../../../../logging'; import { PythonEnvKind } from '../../info'; import { areSameEnv } from '../../info/env'; -import { getPrioritizedEnvKinds } from '../../info/envKind'; +import { sortExtensionSource, sortKindFunction } from '../../info/envKind'; import { BasicEnvInfo, ILocator, @@ -136,7 +136,8 @@ function checkIfFinishedAndNotify( function resolveEnvCollision(oldEnv: BasicEnvInfo, newEnv: BasicEnvInfo): BasicEnvInfo { const [env] = sortEnvInfoByPriority(oldEnv, newEnv); const merged = cloneDeep(env); - merged.source = uniq((oldEnv.source ?? []).concat(newEnv.source ?? [])); + merged.source = union(oldEnv.source ?? [], newEnv.source ?? []); + merged.kind = union(merged.kind, oldEnv.kind, newEnv.kind); return merged; } @@ -145,10 +146,15 @@ function resolveEnvCollision(oldEnv: BasicEnvInfo, newEnv: BasicEnvInfo): BasicE * match the priority in the environment identifier. */ function sortEnvInfoByPriority(...envs: BasicEnvInfo[]): BasicEnvInfo[] { - // TODO: When we consolidate the PythonEnvKind and EnvironmentType we should have - // one location where we define priority. - const envKindByPriority: PythonEnvKind[] = getPrioritizedEnvKinds(); - return envs.sort( - (a: BasicEnvInfo, b: BasicEnvInfo) => envKindByPriority.indexOf(a.kind) - envKindByPriority.indexOf(b.kind), - ); + return envs.sort((a: BasicEnvInfo, b: BasicEnvInfo) => { + const kindDiff = sortKindFunction(getTopKind(a.kind), getTopKind(b.kind)); + if (kindDiff !== 0) { + return kindDiff; + } + return sortExtensionSource(a.extensionId, b.extensionId); + }); +} + +function getTopKind(kinds: PythonEnvKind[]) { + return kinds.sort(sortKindFunction)[0]; } diff --git a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts index 0c10b9482c1e..bc1dc27c870e 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts @@ -66,7 +66,7 @@ export class PythonEnvsResolver implements IResolvingLocator { const [executablePath, envPath] = await getExecutablePathAndEnvPath(path); path = executablePath.length ? executablePath : envPath; const kind = await identifyEnvironment(path); - const environment = await resolveBasicEnv({ kind, executablePath, envPath }); + const environment = await resolveBasicEnv({ kind: [kind], executablePath, envPath }); if (!environment) { return undefined; } diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index ee28436285bf..56ee5e80aa7a 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -26,6 +26,7 @@ import { getRegistryInterpreters, getRegistryInterpretersSync } from '../../../c import { BasicEnvInfo } from '../../locator'; import { parseVersionFromExecutable } from '../../info/executable'; import { traceError, traceWarn } from '../../../../logging'; +import { sortExtensionSource } from '../../info/envKind'; type ResolverType = (_: BasicEnvInfo, useCache?: boolean) => Promise; type ResolversType = { resolver: ResolverType; extensionId?: string }[]; @@ -62,20 +63,18 @@ export function registerResolver( * executable and returns it. Notice `undefined` is never returned, so environment * returned could still be invalid. */ -export async function resolveBasicEnv( - env: BasicEnvInfo, - useCache = false, - extensionId?: string, -): Promise { +export async function resolveBasicEnv(env: BasicEnvInfo, useCache = false): Promise { const { kind, source } = env; - const value = resolvers.get(kind); + const value = resolvers.get(kind[0]); if (!value) { - traceError('No resolver found for kind:', kind); + traceError('No resolver found for kind:', kind[0]); return undefined; } let resolverForKind: ResolverType; if (Array.isArray(value)) { - const resolver = extensionId ? value.find((v) => v.extensionId === extensionId)?.resolver : value[0].resolver; + const resolver = env.extensionId + ? value.find((v) => v.extensionId === env.extensionId)?.resolver + : value.sort((a, b) => sortExtensionSource(a.extensionId, b.extensionId))[0].resolver; if (!resolver) { return undefined; } @@ -136,7 +135,7 @@ async function updateEnvUsingRegistry(env: PythonEnvInfo): Promise { } catch (ex) { version = UNKNOWN_PYTHON_VERSION; } - env.kind = env.kind === PythonEnvKind.Unknown ? PythonEnvKind.OtherGlobal : env.kind; + env.kind = [env.kind[0] === PythonEnvKind.Unknown ? PythonEnvKind.OtherGlobal : env.kind[0]]; env.version = comparePythonVersionSpecificity(version, env.version) > 0 ? version : env.version; env.distro.defaultDisplayName = data.companyDisplayName; env.arch = data.bitnessStr === '32bit' ? Architecture.x86 : Architecture.x64; @@ -185,7 +184,7 @@ async function resolveCondaEnv(env: BasicEnvInfo, useCache?: boolean): Promise

0) { executable = env.executablePath; @@ -194,7 +193,7 @@ async function resolveCondaEnv(env: BasicEnvInfo, useCache?: boolean): Promise

{ const versionStrings = parsePyenvVersion(name); const envInfo = buildEnvInfo({ - kind: PythonEnvKind.Pyenv, + kind: env.kind, executable: executablePath, source: [], location, @@ -272,7 +271,7 @@ async function isBaseCondaPyenvEnvironment(executablePath: string) { async function resolveWindowsStoreEnv(env: BasicEnvInfo): Promise { const { executablePath } = env; return buildEnvInfo({ - kind: PythonEnvKind.WindowsStore, + kind: env.kind, executable: executablePath, version: parsePythonVersionFromPath(executablePath), org: 'Microsoft', diff --git a/src/client/pythonEnvironments/common/environmentIdentifier.ts b/src/client/pythonEnvironments/common/environmentIdentifier.ts index 2e039710e47b..17975b2f83ca 100644 --- a/src/client/pythonEnvironments/common/environmentIdentifier.ts +++ b/src/client/pythonEnvironments/common/environmentIdentifier.ts @@ -3,7 +3,7 @@ import { traceWarn } from '../../logging'; import { PythonEnvKind } from '../base/info'; -import { getPrioritizedEnvKinds } from '../base/info/envKind'; +import { getPrioritizedEnvKinds, sortExtensionSource } from '../base/info/envKind'; import { isCondaEnvironment } from './environmentManagers/conda'; import { isGloballyInstalledEnv } from './environmentManagers/globalInstalledEnvs'; import { isPipenvEnvironment } from './environmentManagers/pipenv'; @@ -61,7 +61,7 @@ export async function identifyEnvironment(path: string): Promise if (value) { let identifier: IdentifierType; if (Array.isArray(value)) { - identifier = value[0].identifier; + identifier = value.sort((a, b) => sortExtensionSource(a.extensionId, b.extensionId))[0].identifier; } else { identifier = value; } From 7d469e358fbbaa0cc458631e181a49a174b5223d Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 19 Aug 2022 12:16:26 -0700 Subject: [PATCH 28/32] Allow low level custom locators to contain a single source --- src/client/pythonEnvironments/base/locator.ts | 2 +- src/client/pythonEnvironments/base/locatorUtils.ts | 2 +- .../pythonEnvironments/base/locators/wrappers.ts | 14 +++++++++----- src/client/pythonEnvironments/converter.ts | 6 +++--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 821fb6cdd695..1479c2e80a93 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -99,7 +99,7 @@ export interface ILocatorAPI { } export type EnvInfo = BaseEnvInfo & { - envSources: EnvSource[]; + envSource: EnvSource[] | EnvSource; }; export type BaseEnvInfo = { diff --git a/src/client/pythonEnvironments/base/locatorUtils.ts b/src/client/pythonEnvironments/base/locatorUtils.ts index 97cb6298416f..ebb01b798552 100644 --- a/src/client/pythonEnvironments/base/locatorUtils.ts +++ b/src/client/pythonEnvironments/base/locatorUtils.ts @@ -26,7 +26,7 @@ export function getQueryFilter(query: PythonLocatorQuery): (env: PythonEnvInfo) if (kinds === undefined) { return true; } - return kinds.includes(env.kind); + return kinds.includes(env.kind[0]); } function checkSearchLocation(env: PythonEnvInfo): boolean { if (env.searchLocation === undefined) { diff --git a/src/client/pythonEnvironments/base/locators/wrappers.ts b/src/client/pythonEnvironments/base/locators/wrappers.ts index 5faee20cfbb3..653cf2c918dd 100644 --- a/src/client/pythonEnvironments/base/locators/wrappers.ts +++ b/src/client/pythonEnvironments/base/locators/wrappers.ts @@ -18,6 +18,7 @@ import { IWorkspaceLocatorFactory, ILocatorRegister, ILocatorFactory, + InternalEnvironmentProviderMetadata, } from '../locator'; import { combineIterators, Locators } from '../locators'; import { LazyResourceBasedLocator } from './common/resourceBasedLocator'; @@ -51,11 +52,11 @@ export class ExtensionLocators extends Locators implements ILocato return combineIterators(iterators); } - public addNewLocator(locatorFactory: ILocatorFactory): void { + public addNewLocator(locatorFactory: ILocatorFactory, metadata: InternalEnvironmentProviderMetadata): void { if (IsNonWorkspaceLocatorFactory(locatorFactory)) { - this.nonWorkspace = [...this.nonWorkspace, new ConvertLocator(locatorFactory())]; + this.nonWorkspace = [...this.nonWorkspace, new ConvertLocator(locatorFactory(), metadata)]; } else { - this.workspace.addNewLocator(locatorFactory); + this.workspace.addNewLocator(locatorFactory, metadata); } } } @@ -158,11 +159,14 @@ export class WorkspaceLocators extends LazyResourceBasedLocator { ); } - public addNewLocator(locatorFactory: IWorkspaceLocatorFactory): void { + public addNewLocator( + locatorFactory: IWorkspaceLocatorFactory, + metadata: InternalEnvironmentProviderMetadata, + ): void { Object.keys(this.roots).forEach((key) => { const root = this.roots[key]; const newLocator = locatorFactory(root.fsPath); - const convertedLocator: ILocator = new ConvertLocator(newLocator); + const convertedLocator: ILocator = new ConvertLocator(newLocator, metadata); const [locators] = this.locators[key]; locators.addLocator(convertedLocator); }); diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts index 12ef07b1ff56..ef5c728f6b43 100644 --- a/src/client/pythonEnvironments/converter.ts +++ b/src/client/pythonEnvironments/converter.ts @@ -60,7 +60,7 @@ function convertDetailsAPI(proposed: ProposedDetailsAPI): InternalDetailsAPI { return undefined; } const envInfo = buildEnvInfo({ - kind: convertKind(details.environment?.source[0] ?? PythonEnvKind.Unknown), + kind: details.environment?.source.map((k) => convertKind(k)), version: details.version, executable: details.executable.path, arch: details.executable.bitness, @@ -165,11 +165,11 @@ export class ConvertLocator implements ILocator { } private convertToBasicEnv(env: EnvInfo): BasicEnvInfo { - // TODO: Support multiple kinds + const sources = Array.isArray(env.envSource) ? env.envSource : [env.envSource]; return { executablePath: env.executablePath, envPath: env.envPath, - kind: convertKind(env.envSources[0]), + kind: sources.map((s) => convertKind(s)), extensionId: this.metadata.extensionId, }; } From b922c1602003fe6429323bf1d856a70ed5edc74f Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 19 Aug 2022 13:11:07 -0700 Subject: [PATCH 29/32] Allow low level locators to emit a single kind --- .../pythonEnvironments/base/info/env.ts | 16 ++++---- src/client/pythonEnvironments/base/locator.ts | 21 +++++++++- .../base/locators/composite/envsReducer.ts | 34 +++++++-------- .../base/locators/composite/envsResolver.ts | 14 +++---- .../base/locators/composite/resolverUtils.ts | 31 +++++++------- src/client/pythonEnvironments/index.ts | 6 +-- src/client/pythonEnvironments/legacyIOC.ts | 2 +- src/test/pythonEnvironments/base/common.ts | 8 ++-- .../composite/envsResolver.unit.test.ts | 18 ++++---- .../composite/resolverUtils.unit.test.ts | 41 +++++++++++-------- 10 files changed, 109 insertions(+), 82 deletions(-) diff --git a/src/client/pythonEnvironments/base/info/env.ts b/src/client/pythonEnvironments/base/info/env.ts index 354befbfa12d..22c5fd27acf5 100644 --- a/src/client/pythonEnvironments/base/info/env.ts +++ b/src/client/pythonEnvironments/base/info/env.ts @@ -20,7 +20,7 @@ import { PythonVersion, virtualEnvKinds, } from '.'; -import { BasicEnvInfo } from '../locator'; +import { CompositeEnvInfo, convertKindIntoArray } from '../locator'; /** * Create a new info object with all values empty. @@ -28,7 +28,7 @@ import { BasicEnvInfo } from '../locator'; * @param init - if provided, these values are applied to the new object */ export function buildEnvInfo(init?: { - kind?: PythonEnvKind[]; + kind?: PythonEnvKind[] | PythonEnvKind; executable?: string; name?: string; location?: string; @@ -83,7 +83,7 @@ export function buildEnvInfo(init?: { export function copyEnvInfo( env: PythonEnvInfo, updates?: { - kind?: PythonEnvKind[]; + kind?: PythonEnvKind[] | PythonEnvKind; }, ): PythonEnvInfo { // We don't care whether or not extra/hidden properties @@ -98,7 +98,7 @@ export function copyEnvInfo( function updateEnv( env: PythonEnvInfo, updates: { - kind?: PythonEnvKind[]; + kind?: PythonEnvKind[] | PythonEnvKind; executable?: string; location?: string; version?: PythonVersion; @@ -106,7 +106,7 @@ function updateEnv( }, ): void { if (updates.kind !== undefined) { - env.kind = updates.kind; + env.kind = convertKindIntoArray(updates.kind); } if (updates.executable !== undefined) { env.executable.filename = updates.executable; @@ -173,7 +173,7 @@ function buildEnvDisplayString(env: PythonEnvInfo, getAllDetails = false): strin * If insufficient data is provided to generate a minimal object, such * that it is not identifiable, then `undefined` is returned. */ -function getMinimalPartialInfo(env: string | PythonEnvInfo | BasicEnvInfo): Partial | undefined { +function getMinimalPartialInfo(env: string | PythonEnvInfo | CompositeEnvInfo): Partial | undefined { if (typeof env === 'string') { if (env === '') { return undefined; @@ -235,8 +235,8 @@ export function getEnvID(interpreterPath: string, envFolderPath?: string): strin * where multiple versions of python executables are all put in the same directory. */ export function areSameEnv( - left: string | PythonEnvInfo | BasicEnvInfo, - right: string | PythonEnvInfo | BasicEnvInfo, + left: string | PythonEnvInfo | CompositeEnvInfo, + right: string | PythonEnvInfo | CompositeEnvInfo, allowPartialMatch = true, ): boolean | undefined { const leftInfo = getMinimalPartialInfo(left); diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 1479c2e80a93..41def3810f98 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -296,14 +296,31 @@ export type PythonLocatorQuery = BasicPythonLocatorQuery & { type QueryForEvent = E extends PythonEnvsChangedEvent ? PythonLocatorQuery : BasicPythonLocatorQuery; -export type BasicEnvInfo = { - kind: PythonEnvKind[]; +export type BasicEnvInfo = { + kind: T; executablePath: string; source?: PythonEnvSource[]; envPath?: string; extensionId?: ExtensionID; }; +/** + * A version of `BasicEnvInfo` used for composite locators. + */ +export type CompositeEnvInfo = BasicEnvInfo; + +export function convertBasicToComposite(env: BasicEnvInfo): CompositeEnvInfo { + env.kind = convertKindIntoArray(env.kind); + return env as CompositeEnvInfo; +} + +export function convertKindIntoArray(kind: PythonEnvKind | PythonEnvKind[]): PythonEnvKind[] { + if (!Array.isArray(kind)) { + kind = [kind]; + } + return kind; +} + /** * A single Python environment locator. * diff --git a/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts b/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts index 5fcc275b095b..c3d3de3c7135 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts @@ -9,6 +9,8 @@ import { areSameEnv } from '../../info/env'; import { sortExtensionSource, sortKindFunction } from '../../info/envKind'; import { BasicEnvInfo, + CompositeEnvInfo, + convertBasicToComposite, ILocator, IPythonEnvsIterator, isProgressEvent, @@ -22,7 +24,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 ILocator { public get onChanged(): Event { return this.parentLocator.onChanged; } @@ -31,8 +33,8 @@ export class PythonEnvsReducer implements ILocator { constructor(private readonly parentLocator: ILocator) {} - public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { - const didUpdate = new EventEmitter | ProgressNotificationEvent>(); + public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + const didUpdate = new EventEmitter | ProgressNotificationEvent>(); const incomingIterator = this.parentLocator.iterEnvs(query); const iterator = iterEnvsIterator(incomingIterator, didUpdate); iterator.onUpdated = didUpdate.event; @@ -42,13 +44,13 @@ export class PythonEnvsReducer implements ILocator { async function* iterEnvsIterator( iterator: IPythonEnvsIterator, - didUpdate: EventEmitter | ProgressNotificationEvent>, -): IPythonEnvsIterator { + didUpdate: EventEmitter | ProgressNotificationEvent>, +): IPythonEnvsIterator { const state = { done: false, pending: 0, }; - const seen: BasicEnvInfo[] = []; + const seen: CompositeEnvInfo[] = []; if (iterator.onUpdated !== undefined) { const listener = iterator.onUpdated((event) => { @@ -66,8 +68,8 @@ async function* iterEnvsIterator( ); } else if (seen[event.index] !== undefined) { const oldEnv = seen[event.index]; - seen[event.index] = event.update; - didUpdate.fire({ index: event.index, old: oldEnv, update: event.update }); + seen[event.index] = convertBasicToComposite(event.update); + didUpdate.fire({ index: event.index, old: oldEnv, update: seen[event.index] }); } else { // This implies a problem in a downstream locator traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`); @@ -81,7 +83,7 @@ async function* iterEnvsIterator( let result = await iterator.next(); while (!result.done) { - const currEnv = result.value; + const currEnv = convertBasicToComposite(result.value); const oldIndex = seen.findIndex((s) => areSameEnv(s, currEnv)); if (oldIndex !== -1) { resolveDifferencesInBackground(oldIndex, currEnv, state, didUpdate, seen).ignoreErrors(); @@ -100,10 +102,10 @@ async function* iterEnvsIterator( async function resolveDifferencesInBackground( oldIndex: number, - newEnv: BasicEnvInfo, + newEnv: CompositeEnvInfo, state: { done: boolean; pending: number }, - didUpdate: EventEmitter | ProgressNotificationEvent>, - seen: BasicEnvInfo[], + didUpdate: EventEmitter | ProgressNotificationEvent>, + seen: CompositeEnvInfo[], ) { state.pending += 1; // It's essential we increment the pending call count before any asynchronus calls in this method. @@ -125,7 +127,7 @@ async function resolveDifferencesInBackground( */ function checkIfFinishedAndNotify( state: { done: boolean; pending: number }, - didUpdate: EventEmitter | ProgressNotificationEvent>, + didUpdate: EventEmitter | ProgressNotificationEvent>, ) { if (state.done && state.pending === 0) { didUpdate.fire({ stage: ProgressReportStage.discoveryFinished }); @@ -133,7 +135,7 @@ function checkIfFinishedAndNotify( } } -function resolveEnvCollision(oldEnv: BasicEnvInfo, newEnv: BasicEnvInfo): BasicEnvInfo { +function resolveEnvCollision(oldEnv: CompositeEnvInfo, newEnv: CompositeEnvInfo): CompositeEnvInfo { const [env] = sortEnvInfoByPriority(oldEnv, newEnv); const merged = cloneDeep(env); merged.source = union(oldEnv.source ?? [], newEnv.source ?? []); @@ -145,8 +147,8 @@ function resolveEnvCollision(oldEnv: BasicEnvInfo, newEnv: BasicEnvInfo): BasicE * Selects an environment based on the environment selection priority. This should * match the priority in the environment identifier. */ -function sortEnvInfoByPriority(...envs: BasicEnvInfo[]): BasicEnvInfo[] { - return envs.sort((a: BasicEnvInfo, b: BasicEnvInfo) => { +function sortEnvInfoByPriority(...envs: CompositeEnvInfo[]): CompositeEnvInfo[] { + return envs.sort((a: CompositeEnvInfo, b: CompositeEnvInfo) => { const kindDiff = sortKindFunction(getTopKind(a.kind), getTopKind(b.kind)); if (kindDiff !== 0) { return kindDiff; diff --git a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts index bc1dc27c870e..869d90a7aed8 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts @@ -9,7 +9,7 @@ import { PythonEnvInfo, PythonEnvKind } from '../../info'; import { getEnvPath, setEnvDisplayString } from '../../info/env'; import { InterpreterInformation } from '../../info/interpreter'; import { - BasicEnvInfo, + CompositeEnvInfo, IInternalEnvironmentProvider, ILocator, InternalEnvironmentProviderMetadata, @@ -22,7 +22,7 @@ import { PythonLocatorQuery, } from '../../locator'; import { PythonEnvsChangedEvent } from '../../watcher'; -import { registerResolver, resolveBasicEnv } from './resolverUtils'; +import { registerResolver, resolveCompositeEnv } from './resolverUtils'; import { traceVerbose, traceWarn } from '../../../../logging'; import { getEnvironmentDirFromPath, getInterpreterPathFromDir, isPythonExecutable } from '../../../common/commonUtils'; import { getEmptyVersion } from '../../info/pythonVersion'; @@ -58,7 +58,7 @@ export class PythonEnvsResolver implements IResolvingLocator { } constructor( - private readonly parentLocator: ILocator, + private readonly parentLocator: ILocator, private readonly environmentInfoService: IEnvironmentInfoService, ) {} @@ -66,7 +66,7 @@ export class PythonEnvsResolver implements IResolvingLocator { const [executablePath, envPath] = await getExecutablePathAndEnvPath(path); path = executablePath.length ? executablePath : envPath; const kind = await identifyEnvironment(path); - const environment = await resolveBasicEnv({ kind: [kind], executablePath, envPath }); + const environment = await resolveCompositeEnv({ kind: [kind], executablePath, envPath }); if (!environment) { return undefined; } @@ -86,7 +86,7 @@ export class PythonEnvsResolver implements IResolvingLocator { } private async *iterEnvsIterator( - iterator: IPythonEnvsIterator, + iterator: IPythonEnvsIterator, didUpdate: EventEmitter, ): IPythonEnvsIterator { const state = { @@ -112,7 +112,7 @@ export class PythonEnvsResolver implements IResolvingLocator { ); } else if (seen[event.index] !== undefined) { const old = seen[event.index]; - const env = await resolveBasicEnv(event.update, true); + const env = await resolveCompositeEnv(event.update, true); didUpdate.fire({ old, index: event.index, update: env }); if (env) { seen[event.index] = env; @@ -132,7 +132,7 @@ export class PythonEnvsResolver implements IResolvingLocator { let result = await iterator.next(); while (!result.done) { // Use cache from the current refresh where possible. - const currEnv = await resolveBasicEnv(result.value, true); + const currEnv = await resolveCompositeEnv(result.value, true); if (currEnv) { seen.push(currEnv); yield currEnv; diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index 56ee5e80aa7a..e3d020fd51eb 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -23,12 +23,12 @@ import { getPyenvVersionsDir, parsePyenvVersion } from '../../../common/environm import { Architecture, getOSType, OSType } from '../../../../common/utils/platform'; import { getPythonVersionFromPath as parsePythonVersionFromPath, parseVersion } from '../../info/pythonVersion'; import { getRegistryInterpreters, getRegistryInterpretersSync } from '../../../common/windowsUtils'; -import { BasicEnvInfo } from '../../locator'; +import { CompositeEnvInfo } from '../../locator'; import { parseVersionFromExecutable } from '../../info/executable'; import { traceError, traceWarn } from '../../../../logging'; import { sortExtensionSource } from '../../info/envKind'; -type ResolverType = (_: BasicEnvInfo, useCache?: boolean) => Promise; +type ResolverType = (_: CompositeEnvInfo, useCache?: boolean) => Promise; type ResolversType = { resolver: ResolverType; extensionId?: string }[]; const resolvers = new Map(); Object.values(PythonEnvKind).forEach((k) => { @@ -43,7 +43,7 @@ resolvers.set(PythonEnvKind.Pyenv, resolvePyenvEnv); export function registerResolver( kind: PythonEnvKind, - resolver: (_: BasicEnvInfo, useCache?: boolean) => Promise, + resolver: (_: CompositeEnvInfo, useCache?: boolean) => Promise, extensionId: string, ): void { const resolversForKind = resolvers.get(kind); @@ -59,16 +59,19 @@ export function registerResolver( } /** - * Find as much info about the given Basic Python env as possible without running the + * Find as much info about the given Python env as possible without running the * executable and returns it. Notice `undefined` is never returned, so environment * returned could still be invalid. */ -export async function resolveBasicEnv(env: BasicEnvInfo, useCache = false): Promise { +export async function resolveCompositeEnv(env: CompositeEnvInfo, useCache = false): Promise { const { kind, source } = env; - const value = resolvers.get(kind[0]); + let value = resolvers.get(kind[0]); if (!value) { - traceError('No resolver found for kind:', kind[0]); - return undefined; + value = env.extensionId ? resolvers.get(env.extensionId as PythonEnvKind) : undefined; + if (!value) { + traceError('No resolver found for env:', JSON.stringify(env)); + return undefined; + } } let resolverForKind: ResolverType; if (Array.isArray(value)) { @@ -146,7 +149,7 @@ async function updateEnvUsingRegistry(env: PythonEnvInfo): Promise { } } -async function resolveGloballyInstalledEnv(env: BasicEnvInfo): Promise { +async function resolveGloballyInstalledEnv(env: CompositeEnvInfo): Promise { const { executablePath } = env; let version; try { @@ -162,7 +165,7 @@ async function resolveGloballyInstalledEnv(env: BasicEnvInfo): Promise { +async function resolveSimpleEnv(env: CompositeEnvInfo): Promise { const { executablePath, kind } = env; const envInfo = buildEnvInfo({ kind, @@ -175,7 +178,7 @@ async function resolveSimpleEnv(env: BasicEnvInfo): Promise { return envInfo; } -async function resolveCondaEnv(env: BasicEnvInfo, useCache?: boolean): Promise { +async function resolveCondaEnv(env: CompositeEnvInfo, useCache?: boolean): Promise { const { executablePath } = env; const conda = await Conda.getConda(); if (conda === undefined) { @@ -184,7 +187,7 @@ async function resolveCondaEnv(env: BasicEnvInfo, useCache?: boolean): Promise

0) { executable = env.executablePath; @@ -215,7 +218,7 @@ async function resolveCondaEnv(env: BasicEnvInfo, useCache?: boolean): Promise

{ +async function resolvePyenvEnv(env: CompositeEnvInfo): Promise { const { executablePath } = env; const location = getEnvironmentDirFromPath(executablePath); const name = path.basename(location); @@ -268,7 +271,7 @@ async function isBaseCondaPyenvEnvironment(executablePath: string) { return arePathsSame(path.dirname(location), pyenvVersionDir); } -async function resolveWindowsStoreEnv(env: BasicEnvInfo): Promise { +async function resolveWindowsStoreEnv(env: CompositeEnvInfo): Promise { const { executablePath } = env; return buildEnvInfo({ kind: env.kind, diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index 49f791be59fc..ddd0f22195ad 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/client/pythonEnvironments/legacyIOC.ts b/src/client/pythonEnvironments/legacyIOC.ts index c1d0b0705983..761259fdcb02 100644 --- a/src/client/pythonEnvironments/legacyIOC.ts +++ b/src/client/pythonEnvironments/legacyIOC.ts @@ -51,7 +51,7 @@ function convertEnvInfo(info: PythonEnvInfo): PythonEnvironment { architecture: arch, }; - const envType = convertedKinds.get(kind); + const envType = convertedKinds.get(kind[0]); if (envType !== undefined) { env.envType = envType; } diff --git a/src/test/pythonEnvironments/base/common.ts b/src/test/pythonEnvironments/base/common.ts index 603f423037bc..5193a45dbb07 100644 --- a/src/test/pythonEnvironments/base/common.ts +++ b/src/test/pythonEnvironments/base/common.ts @@ -17,7 +17,7 @@ import { import { buildEnvInfo } from '../../../client/pythonEnvironments/base/info/env'; import { getEmptyVersion, parseVersion } from '../../../client/pythonEnvironments/base/info/pythonVersion'; import { - BasicEnvInfo, + CompositeEnvInfo, IPythonEnvsIterator, isProgressEvent, Locator, @@ -53,7 +53,7 @@ export function createLocatedEnv( ? getEmptyVersion() // an empty version : parseVersion(versionStr); const env = buildEnvInfo({ - kind, + kind: [kind], executable, location, version, @@ -71,8 +71,8 @@ export function createBasicEnv( executablePath: string, source?: PythonEnvSource[], envPath?: string, -): BasicEnvInfo { - const basicEnv = { executablePath, kind, source, envPath }; +): CompositeEnvInfo { + const basicEnv = { executablePath, kind: [kind], source, envPath }; if (!source) { delete basicEnv.source; } diff --git a/src/test/pythonEnvironments/base/locators/composite/envsResolver.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/envsResolver.unit.test.ts index 053c1f0cc4c8..f4ef65c254e5 100644 --- a/src/test/pythonEnvironments/base/locators/composite/envsResolver.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/envsResolver.unit.test.ts @@ -18,7 +18,7 @@ import { } from '../../../../../client/pythonEnvironments/base/info'; import { getEmptyVersion, parseVersion } from '../../../../../client/pythonEnvironments/base/info/pythonVersion'; import { - BasicEnvInfo, + CompositeEnvInfo, isProgressEvent, ProgressNotificationEvent, ProgressReportStage, @@ -80,7 +80,7 @@ suite('Python envs locator - Environments Resolver', () => { return { name, location, - kind, + kind: [kind], executable: { filename: interpreterPath, sysPrefix: '', @@ -130,7 +130,7 @@ suite('Python envs locator - Environments Resolver', () => { "Python ('win1': venv)", ); const envsReturnedByParentLocator = [env1]; - const parentLocator = new SimpleLocator(envsReturnedByParentLocator); + const parentLocator = new SimpleLocator(envsReturnedByParentLocator); const resolver = new PythonEnvsResolver(parentLocator, envInfoService); const iterator = resolver.iterEnvs(); @@ -153,7 +153,7 @@ suite('Python envs locator - Environments Resolver', () => { path.join(testVirtualHomeDir, '.venvs', 'win1'), ); const envsReturnedByParentLocator = [env1]; - const parentLocator = new SimpleLocator(envsReturnedByParentLocator); + const parentLocator = new SimpleLocator(envsReturnedByParentLocator); const resolver = new PythonEnvsResolver(parentLocator, envInfoService); const iterator = resolver.iterEnvs(); @@ -179,7 +179,7 @@ suite('Python envs locator - Environments Resolver', () => { path.join(testVirtualHomeDir, '.venvs', 'win1', 'python.exe'), ); const envsReturnedByParentLocator = [env1]; - const parentLocator = new SimpleLocator(envsReturnedByParentLocator); + const parentLocator = new SimpleLocator(envsReturnedByParentLocator); const resolver = new PythonEnvsResolver(parentLocator, envInfoService); // Act @@ -208,8 +208,8 @@ suite('Python envs locator - Environments Resolver', () => { path.join(testVirtualHomeDir, '.venvs', 'win1'), ); const envsReturnedByParentLocator = [env]; - const didUpdate = new EventEmitter | ProgressNotificationEvent>(); - const parentLocator = new SimpleLocator(envsReturnedByParentLocator, { + const didUpdate = new EventEmitter | ProgressNotificationEvent>(); + const parentLocator = new SimpleLocator(envsReturnedByParentLocator, { onUpdated: didUpdate.event, }); const resolver = new PythonEnvsResolver(parentLocator, envInfoService); @@ -254,8 +254,8 @@ suite('Python envs locator - Environments Resolver', () => { path.join(testVirtualHomeDir, '.venvs', 'win1', 'python.exe'), ); const envsReturnedByParentLocator = [env]; - const didUpdate = new EventEmitter | ProgressNotificationEvent>(); - const parentLocator = new SimpleLocator(envsReturnedByParentLocator, { + const didUpdate = new EventEmitter | ProgressNotificationEvent>(); + const parentLocator = new SimpleLocator(envsReturnedByParentLocator, { onUpdated: didUpdate.event, }); const resolver = new PythonEnvsResolver(parentLocator, envInfoService); diff --git a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts index 0e9f32ecffcb..ca56fb8dc587 100644 --- a/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/composite/resolverUtils.unit.test.ts @@ -24,7 +24,12 @@ import { AnacondaCompanyName, CondaInfo, } from '../../../../../client/pythonEnvironments/common/environmentManagers/conda'; -import { resolveBasicEnv } from '../../../../../client/pythonEnvironments/base/locators/composite/resolverUtils'; +import { resolveCompositeEnv } from '../../../../../client/pythonEnvironments/base/locators/composite/resolverUtils'; +import { BasicEnvInfo, convertBasicToComposite } from '../../../../../client/pythonEnvironments/base/locator'; + +function resolveEnv(env: BasicEnvInfo) { + return resolveCompositeEnv(convertBasicToComposite(env)); +} suite('Resolver Utils', () => { let getWorkspaceFolders: sinon.SinonStub; @@ -87,7 +92,7 @@ suite('Resolver Utils', () => { const executablePath = path.join(testPyenvVersionsDir, '3.9.0', 'bin', 'python'); const expected = getExpectedPyenvInfo1(); - const actual = await resolveBasicEnv({ executablePath, kind: PythonEnvKind.Pyenv }); + const actual = await resolveEnv({ executablePath, kind: PythonEnvKind.Pyenv }); assertEnvEqual(actual, expected); }); @@ -96,7 +101,7 @@ suite('Resolver Utils', () => { const executablePath = path.join(testPyenvVersionsDir, 'miniconda3-4.7.12', 'bin', 'python'); const expected = getExpectedPyenvInfo2(); - const actual = await resolveBasicEnv({ executablePath, kind: PythonEnvKind.Pyenv }); + const actual = await resolveEnv({ executablePath, kind: PythonEnvKind.Pyenv }); assertEnvEqual(actual, expected); }); }); @@ -147,14 +152,14 @@ suite('Resolver Utils', () => { searchLocation: undefined, name: '', location: '', - kind: PythonEnvKind.WindowsStore, + kind: [PythonEnvKind.WindowsStore], distro: { org: 'Microsoft' }, source: [PythonEnvSource.PathEnvVar], ...createExpectedInterpreterInfo(python38path), }; setEnvDisplayString(expected); - const actual = await resolveBasicEnv({ + const actual = await resolveEnv({ executablePath: python38path, kind: PythonEnvKind.WindowsStore, }); @@ -169,14 +174,14 @@ suite('Resolver Utils', () => { searchLocation: undefined, name: '', location: '', - kind: PythonEnvKind.WindowsStore, + kind: [PythonEnvKind.WindowsStore], distro: { org: 'Microsoft' }, source: [PythonEnvSource.PathEnvVar], ...createExpectedInterpreterInfo(python38path), }; setEnvDisplayString(expected); - const actual = await resolveBasicEnv({ + const actual = await resolveEnv({ executablePath: python38path, kind: PythonEnvKind.WindowsStore, }); @@ -223,7 +228,7 @@ suite('Resolver Utils', () => { const info: PythonEnvInfo = { name, location, - kind, + kind: [kind], executable: { filename: interpreterPath, sysPrefix: '', @@ -253,7 +258,7 @@ suite('Resolver Utils', () => { } throw new Error(`${command} is missing or is not executable`); }); - const actual = await resolveBasicEnv({ + const actual = await resolveEnv({ executablePath: path.join(TEST_LAYOUT_ROOT, 'conda1', 'python.exe'), kind: PythonEnvKind.Conda, }); @@ -268,7 +273,7 @@ suite('Resolver Utils', () => { } throw new Error(`${command} is missing or is not executable`); }); - const actual = await resolveBasicEnv({ + const actual = await resolveEnv({ executablePath: path.join(TEST_LAYOUT_ROOT, 'conda2', 'bin', 'python'), kind: PythonEnvKind.Conda, }); @@ -283,7 +288,7 @@ suite('Resolver Utils', () => { sinon.stub(externalDependencies, 'exec').callsFake(async (command: string) => { throw new Error(`${command} is missing or is not executable`); }); - const actual = await resolveBasicEnv({ + const actual = await resolveEnv({ executablePath: path.join(TEST_LAYOUT_ROOT, 'conda1', 'python.exe'), kind: PythonEnvKind.Conda, }); @@ -320,7 +325,7 @@ suite('Resolver Utils', () => { const info: PythonEnvInfo = { name, location, - kind, + kind: [kind], executable: { filename: interpreterPath, sysPrefix: '', @@ -346,7 +351,7 @@ suite('Resolver Utils', () => { 'win1', path.join(testVirtualHomeDir, '.venvs', 'win1'), ); - const actual = await resolveBasicEnv({ + const actual = await resolveEnv({ executablePath: path.join(testVirtualHomeDir, '.venvs', 'win1', 'python.exe'), kind: PythonEnvKind.Venv, }); @@ -375,7 +380,7 @@ suite('Resolver Utils', () => { const info: PythonEnvInfo = { name, location, - kind, + kind: [kind], executable: { filename: interpreterPath, sysPrefix: '', @@ -396,7 +401,7 @@ suite('Resolver Utils', () => { test('resolveEnv', async () => { const executable = path.join(testLocation3, 'python3.8'); const expected = createExpectedEnvInfo(executable, PythonEnvKind.OtherGlobal, parseVersion('3.8')); - const actual = await resolveBasicEnv({ + const actual = await resolveEnv({ executablePath: executable, kind: PythonEnvKind.OtherGlobal, }); @@ -564,7 +569,7 @@ suite('Resolver Utils', () => { test('If data provided by registry is more informative than kind resolvers, use it to update environment (64bit)', async () => { const interpreterPath = path.join(regTestRoot, 'py39', 'python.exe'); - const actual = await resolveBasicEnv({ + const actual = await resolveEnv({ executablePath: interpreterPath, kind: PythonEnvKind.Unknown, source: [PythonEnvSource.WindowsRegistry], @@ -584,7 +589,7 @@ suite('Resolver Utils', () => { test('If data provided by registry is more informative than kind resolvers, use it to update environment (32bit)', async () => { const interpreterPath = path.join(regTestRoot, 'python38', 'python.exe'); - const actual = await resolveBasicEnv({ + const actual = await resolveEnv({ executablePath: interpreterPath, kind: PythonEnvKind.Unknown, source: [PythonEnvSource.WindowsRegistry, PythonEnvSource.PathEnvVar], @@ -607,7 +612,7 @@ suite('Resolver Utils', () => { throw new Error(`${command} is missing or is not executable`); }); const interpreterPath = path.join(regTestRoot, 'conda3', 'python.exe'); - const actual = await resolveBasicEnv({ + const actual = await resolveEnv({ executablePath: interpreterPath, kind: PythonEnvKind.Conda, source: [PythonEnvSource.WindowsRegistry], From 45468f4b17be6a48f0e5d331459f206a021cae42 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 19 Aug 2022 13:49:18 -0700 Subject: [PATCH 30/32] Update proposed types --- src/client/proposedApiTypes.ts | 165 ++++++++++-------- src/client/pythonEnvironments/base/locator.ts | 12 +- src/client/pythonEnvironments/converter.ts | 9 +- 3 files changed, 110 insertions(+), 76 deletions(-) diff --git a/src/client/proposedApiTypes.ts b/src/client/proposedApiTypes.ts index a57f310fd0d5..4964a331cfb2 100644 --- a/src/client/proposedApiTypes.ts +++ b/src/client/proposedApiTypes.ts @@ -1,12 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -/* eslint-disable @typescript-eslint/ban-types */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { Event, Uri } from 'vscode'; +import { Event, Uri, WorkspaceFolder } from 'vscode'; // https://github.com/microsoft/vscode-python/wiki/Proposed-Environment-APIs @@ -15,7 +10,7 @@ export interface IProposedExtensionAPI { /** * This event is triggered when the active environment changes. */ - onDidActiveEnvironmentChanged: Event; + onDidChangeActiveEnvironment: Event; /** * Returns the path to the python binary selected by the user or as in the settings. * This is just the path to the python binary, this does not provide activation or any @@ -24,44 +19,41 @@ export interface IProposedExtensionAPI { * returns what ever is set for the workspace. * @param resource : Uri of a file or workspace */ - getActiveEnvironmentPath(resource?: Resource): Promise; + getActiveEnvironmentPath(resource?: Resource): Promise; /** - * Returns details for the given interpreter. Details such as absolute interpreter path, + * Returns details for the given python executable. Details such as absolute python executable path, * version, type (conda, pyenv, etc). Metadata such as `sysPrefix` can be found under * metadata field. - * @param path : Full path to environment folder or interpreter whose details you need. + * @param pathID : Full path to environment folder or python executable whose details you need. * @param options : [optional] * * useCache : When true, cache is checked first for any data, returns even if there * is partial data. */ getEnvironmentDetails( - path: string, + pathID: UniquePathType | EnvironmentPath, options?: EnvironmentDetailsOptions, ): Promise; /** * Sets the active environment path for the python extension for the resource. Configuration target * will always be the workspace folder. - * @param path : Full path to environment folder or interpreter to set. + * @param pathID : Full path to environment folder or python executable to set. * @param resource : [optional] Uri of a file ro workspace to scope to a particular workspace * folder. */ - setActiveEnvironment(path: string, resource?: Resource): Promise; + setActiveEnvironment(pathID: UniquePathType | EnvironmentPath, resource?: Resource): Promise; locator: { /** * Returns paths to environments that uniquely identifies an environment found by the extension - * at the time of calling. This API will *not* trigger a refresh. If a refresh is going on it - * will *not* wait for the refresh to finish. This will return what is known so far. To get + * at the time of calling. It returns the values currently in memory. This API will *not* trigger a refresh. If a refresh is going on it + * will *not* wait for the refresh to finish. This will return what is known so far. To get * complete list `await` on promise returned by `getRefreshPromise()`. - * - * Environments lacking an interpreter are identified by environment folder paths, - * whereas other envs can be identified using executable path. */ - getEnvironmentPaths(): Promise; + getEnvironmentPaths(): EnvironmentPath[] | undefined; /** * This event is triggered when the known environment list changes, like when a environment * is found, existing environment is removed, or some details changed on an environment. */ - onDidEnvironmentsChanged: Event; + onDidChangeEnvironments: Event; /** * This API will re-trigger environment discovery. Extensions can wait on the returned * promise to get the updated environment list. If there is a refresh already going on @@ -70,12 +62,12 @@ export interface IProposedExtensionAPI { * * clearCache : When true, this will clear the cache before environment refresh * is triggered. */ - refreshEnvironments(options?: RefreshEnvironmentsOptions): Promise; + refreshEnvironments(options?: RefreshEnvironmentsOptions): Promise; /** * Returns a promise for the ongoing refresh. Returns `undefined` if there are no active * refreshes going on. */ - getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise | undefined; + getRefreshPromise(options?: GetRefreshPromiseOptions): Promise | undefined; /** * Tracks discovery progress for current list of known environments, i.e when it starts, finishes or any other relevant * stage. Note the progress for a particular query is currently not tracked or reported, this only indicates progress of @@ -92,26 +84,11 @@ export enum Architecture { x64 = 3, } -export type EnvSource = KnownEnvSourceTypes | string; - -export enum KnownEnvSourceTypes { - Conda = 'Conda', - Pipenv = 'PipEnv', - Poetry = 'Poetry', - VirtualEnv = 'VirtualEnv', - Venv = 'Venv', - VirtualEnvWrapper = 'VirtualEnvWrapper', - Pyenv = 'Pyenv', -} +export type EnvSource = KnownEnvSources | string; +export type KnownEnvSources = 'Conda' | 'Pipenv' | 'Poetry' | 'VirtualEnv' | 'Venv' | 'VirtualEnvWrapper' | 'Pyenv'; export type EnvType = KnownEnvTypes | string; - -export enum KnownEnvTypes { - VirtualEnv = 'VirtualEnv', - Conda = 'Conda', - Unknown = 'Unknown', - Global = 'Global', -} +export type KnownEnvTypes = 'VirtualEnv' | 'Conda' | 'Unknown'; export type BasicVersionInfo = { major: number; @@ -141,27 +118,35 @@ export type StandardVersionInfo = BasicVersionInfo & { release?: PythonVersionRelease; }; +// To be added later: +// run: { +// exec: Function; +// shellExec: Function; +// execObservable: Function; +// terminalExec: () => void; +// env?: { [key: string]: string | null | undefined }; +// }; + export interface EnvironmentDetails { executable: { path: string; bitness?: Architecture; sysPrefix: string; - // To be added later: - // run: { - // exec: Function; - // shellExec: Function; - // execObservable: Function; - // terminalExec: () => void; - // env?: { [key: string]: string | null | undefined }; - // }; - }; - environment?: { - type: EnvType; - name?: string; - path: string; - project?: string; // Any specific project environment is created for. - source: EnvSource[]; }; + environment: + | { + type: EnvType; + name?: string; + folderPath: string; + /** + * Any specific workspace folder this environment is created for. + * What if that workspace folder is not opened yet? We should still provide a workspace folder so it can be filtered out. + * WorkspaceFolder type won't work as it assumes the workspace is opened, hence using URI. + */ + workspaceFolder?: Uri; + source: EnvSource[]; + } + | undefined; version: StandardVersionInfo & { sysVersion?: string; }; @@ -173,10 +158,13 @@ export interface EnvironmentDetails { } export interface EnvironmentDetailsOptions { + /** + * When true, cache is checked first for any data, returns even if there is partial data. + */ useCache: boolean; } -export interface GetRefreshEnvironmentsOptions { +export interface GetRefreshPromiseOptions { /** * Get refresh promise which resolves once the following stage has been reached for the list of known environments. */ @@ -193,30 +181,69 @@ export type ProgressNotificationEvent = { stage: ProgressReportStage; }; -export type Resource = Uri | undefined; +/** + * Uri of a file inside a workspace or workspace folder itself. + */ +export type Resource = Uri | WorkspaceFolder; /** - * Path to environment folder or path to interpreter that uniquely identifies an environment. - * Environments lacking an interpreter are identified by environment folder paths, - * whereas other envs can be identified using interpreter path. + * Path to environment folder or path to python executable that uniquely identifies an environment. + * Environments lacking a python executable are identified by environment folder paths, + * whereas other envs can be identified using python executable path. */ export type UniquePathType = string; -export interface EnvPathType { - path: UniquePathType; - pathType: 'envFolderPath' | 'interpreterPath'; +export interface EnvironmentPath { + pathID: UniquePathType; + /** + * Path to python executable that uniquely identifies an environment. + * Carries `undefined` if an executable cannot uniquely identify an + * environment or does not exist within the env. + */ + executablePath: string | undefined; } -export interface EnvironmentsChangedParams { - path?: UniquePathType; - type: 'add' | 'remove' | 'update' | 'clear-all'; -} +export type EnvironmentsChangedParams = + | ({ + /** + * * "add": New environment is added. + * * "remove": Existing environment in the list is removed. + * * "update": New information found about existing environment. + */ + type: 'add' | 'remove' | 'update'; + } & EnvironmentPath) + | { + /** + * * "clear-all": Remove all of the items in the list. (This is fired when a hard refresh is triggered) + */ + type: 'clear-all'; + } + | { + /** + * The location at which the environment got created. + */ + location: string; + /** + * * "created": New environment is created in some location. + */ + type: 'created'; + }; export interface ActiveEnvironmentChangedParams { - path: UniquePathType; - resource?: Uri; + pathID: UniquePathType; + /** + * Workspace folder the environment changed for. + */ + resource?: WorkspaceFolder; } export interface RefreshEnvironmentsOptions { + /** + * When `true`, this will clear the cache before environment refresh is triggered. + */ clearCache?: boolean; + /** + * Only trigger a refresh if it hasn't already been triggered for this session. + */ + ifNotTriggerredAlready?: boolean; } diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index 41def3810f98..3ecc5109c6e8 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -146,14 +146,18 @@ interface EnvironmentMetaData { } export interface LocatorEnvsChangedEvent { - /** - * Any details known about the environment which can be used for query. - */ - env?: EnvironmentMetaData; /** * Details about how the environment was modified. * */ type: EnvChangeType; + /** + * The unique ID for the environment affected. + */ + pathId: UniquePathType; + /** + * Any other details known about the environment which can be used for query. + */ + env?: EnvironmentMetaData; } export type EnvChangeType = 'add' | 'remove' | 'update'; diff --git a/src/client/pythonEnvironments/converter.ts b/src/client/pythonEnvironments/converter.ts index ef5c728f6b43..5cab4b803059 100644 --- a/src/client/pythonEnvironments/converter.ts +++ b/src/client/pythonEnvironments/converter.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { EventEmitter, Event } from 'vscode'; +import { EventEmitter, Event, Uri } from 'vscode'; import { FileChangeType } from '../common/platform/fileSystemWatcher'; import { traceVerbose } from '../logging'; import { PythonEnvInfo, PythonEnvKind } from './base/info'; @@ -97,8 +97,11 @@ export class ConvertLocator implements ILocator { ) { if (parentLocator.onChanged) { parentLocator.onChanged((e: LocatorEnvsChangedEvent) => { - const event: PythonEnvsChangedEvent = { type: this.eventKeys[`${e.type}`] }; - // TODO: Add translation for other events. + const event: PythonEnvsChangedEvent = { + type: this.eventKeys[`${e.type}`], + kind: e.env?.envSources ? convertKind(e.env?.envSources[0]) : undefined, + searchLocation: Uri.file(e.pathId), + }; this.didChange.fire(event); }); } From 6e244f250a3151c748954d82e49cc524e17f631e Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 2 Sep 2022 16:37:10 -0700 Subject: [PATCH 31/32] Fix errors --- src/client/interpreter/interpreterService.ts | 6 ++--- src/client/proposedApiTypes.ts | 2 +- .../locators/composite/envsCollectionCache.ts | 22 +++++++++++++++---- .../base/locators/composite/resolverUtils.ts | 2 +- src/client/pythonEnvironments/info/index.ts | 1 - 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/client/interpreter/interpreterService.ts b/src/client/interpreter/interpreterService.ts index 1f563125162d..d7e638a6ed93 100644 --- a/src/client/interpreter/interpreterService.ts +++ b/src/client/interpreter/interpreterService.ts @@ -11,7 +11,7 @@ import { Uri, } from 'vscode'; import '../common/extensions'; -import { IApplicationShell, IDocumentManager } from '../common/application/types'; +import { IApplicationShell, IDocumentManager, IWorkspaceService } from '../common/application/types'; import { IConfigurationService, IDisposableRegistry, @@ -221,8 +221,8 @@ export class InterpreterService implements Disposable, IInterpreterService { this._pythonPathSetting = pySettings.pythonPath; this.didChangeInterpreterEmitter.fire(); reportActiveInterpreterChanged({ - path: pySettings.pythonPath, - resource, + pathID: pySettings.pythonPath, + resource: this.serviceContainer.get(IWorkspaceService).getWorkspaceFolder(resource), }); const interpreterDisplay = this.serviceContainer.get(IInterpreterDisplay); interpreterDisplay.refresh().catch((ex) => traceError('Python Extension: display.refresh', ex)); diff --git a/src/client/proposedApiTypes.ts b/src/client/proposedApiTypes.ts index 4964a331cfb2..e65f9ca5bbca 100644 --- a/src/client/proposedApiTypes.ts +++ b/src/client/proposedApiTypes.ts @@ -234,7 +234,7 @@ export interface ActiveEnvironmentChangedParams { /** * Workspace folder the environment changed for. */ - resource?: WorkspaceFolder; + resource: WorkspaceFolder | undefined; } export interface RefreshEnvironmentsOptions { diff --git a/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts b/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts index 7c12faf524c4..778d2f6f55bf 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts @@ -96,8 +96,13 @@ export class PythonEnvInfoCache extends PythonEnvsWatcher { const env = this.envs.splice(index, 1)[0]; this.fire({ old: env, new: undefined }); + const envPath = getEnvPath(env.executable.filename, env.location); reportInterpretersChanged([ - { path: getEnvPath(env.executable.filename, env.location).path, type: 'remove' }, + { + pathID: envPath.path, + type: 'remove', + executablePath: envPath.pathType === 'envFolderPath' ? undefined : env.executable.filename, + }, ]); }); } @@ -115,7 +120,14 @@ export class PythonEnvInfoCache extends PythonEnvsWatcher { this.fire({ old: e, new: undefined }); }); - reportInterpretersChanged([{ path: undefined, type: 'clear-all' }]); + reportInterpretersChanged([{ type: 'clear-all' }]); this.envs = []; return Promise.resolve(); } diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts index 72930f460e28..fa0a87421357 100644 --- a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -269,7 +269,7 @@ async function isBaseCondaPyenvEnvironment(executablePath: string) { return arePathsSame(path.dirname(location), pyenvVersionDir); } -async function resolveMicrosoftStoreEnv(env: BasicEnvInfo): Promise { +async function resolveMicrosoftStoreEnv(env: CompositeEnvInfo): Promise { const { executablePath } = env; return buildEnvInfo({ kind: env.kind, diff --git a/src/client/pythonEnvironments/info/index.ts b/src/client/pythonEnvironments/info/index.ts index 9171d9148474..aeb04f988ad8 100644 --- a/src/client/pythonEnvironments/info/index.ts +++ b/src/client/pythonEnvironments/info/index.ts @@ -17,7 +17,6 @@ export enum EnvironmentType { // Simple virtual envs VirtualEnv = 'VirtualEnv', Venv = 'Venv', - Poetry = 'Poetry', VirtualEnvWrapper = 'VirtualEnvWrapper', // Global Global = 'Global', From fb65e79d84e779a0861f3ab0f2bdc497f9ecac87 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 2 Sep 2022 17:24:57 -0700 Subject: [PATCH 32/32] Updates --- src/client/proposedApiTypes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/proposedApiTypes.ts b/src/client/proposedApiTypes.ts index e65f9ca5bbca..3b3c9c125106 100644 --- a/src/client/proposedApiTypes.ts +++ b/src/client/proposedApiTypes.ts @@ -124,7 +124,7 @@ export type StandardVersionInfo = BasicVersionInfo & { // shellExec: Function; // execObservable: Function; // terminalExec: () => void; -// env?: { [key: string]: string | null | undefined }; +// env?: { [key: string]: string | null | undefined }; // Should be specific to the current terminal?? // }; export interface EnvironmentDetails {